diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..26d33521af10bcc7fd8cea344038eaaeb78d0ef5 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..105ce2da2d6447d11dfe32bfb846c3d5b199fc99 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000000000000000000000000000000000..d1e22ecb89619a9c2dcf51a28d891a196d2462a0 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..8a06bd9193c0ff24acc14b653d2d6b218f06279b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/ngsi-ld-test-suite.iml b/.idea/ngsi-ld-test-suite.iml new file mode 100644 index 0000000000000000000000000000000000000000..d0876a78d06ac03b5d78c8dcdb95570281c6f1d6 --- /dev/null +++ b/.idea/ngsi-ld-test-suite.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..94a25f7f4cb416c083d265558da75d457237d671 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_01.robot b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_01.robot new file mode 100644 index 0000000000000000000000000000000000000000..6f13b97220b15fc6390b9815648de38d565eada0 --- /dev/null +++ b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_01.robot @@ -0,0 +1,45 @@ +*** Settings *** +Documentation Check that you can query context source registrations if at least one of list of Entity Types or list of Attribute names is present +Resource ${EXECDIR}/resources/ApiUtils.resource +Resource ${EXECDIR}/resources/AssertionUtils.resource +Resource ${EXECDIR}/resources/JsonUtils.resource + +Test Template Query Context Source Registration +Suite Setup Setup Initial Context Source Registrations +Suite Teardown Delete Created Context Source Registrations + +*** Variable *** +${context_source_registration_id_prefix}= urn:ngsi-ld:ContextSourceRegistration: +${first_context_source_registration_payload_file_path}= csourceRegistrations/context-source-registration-sample.jsonld +${second_context_source_registration_payload_file_path}= csourceRegistrations/context-source-registration-detailed-information-sample.jsonld + +*** Test Cases *** QUERY_PARAM_NAME QUERY_PARAM_VALUE EXPECTATION_FILE_PATH EXPECTED_CONTEXT_SOURCE_REGISTRATION_IDS +With list of entity types type Building csourceRegistrations/expectations/context-source-registrations-037-01-expectation.json ${first_context_source_registration_id} ${second_context_source_registration_id} +With list of attribute names attrs name csourceRegistrations/expectations/context-source-registrations-037-01-expectation.json ${second_context_source_registration_id} + +*** Keywords *** +Query Context Source Registration + [Arguments] ${query_param_name} ${query_param_value} ${expectation_file_path} @{expected_context_source_registration_ids} + [Documentation] Check that you can query context source registrations if at least one of list of Entity Types or list of Attribute names is present + [Tags] mandatory + + Query Context Source Registrations context=${ngsild_test_suite_context} ${query_param_name}=${query_param_value} + + Check Response Status Code Set To 200 + Check Response Body Containing List Containing Context Source Registrations elements ${expectation_file_path} ${expected_context_source_registration_ids} + +Setup Initial Context Source Registrations + ${first_context_source_registration_id}= Generate Random Entity Id ${context_source_registration_id_prefix} + ${second_context_source_registration_id}= Generate Random Entity Id ${context_source_registration_id_prefix} + ${first_context_source_registration_payload}= Load Test Sample ${first_context_source_registration_payload_file_path} ${first_context_source_registration_id} + ${second_context_source_registration_payload}= Load Test Sample ${second_context_source_registration_payload_file_path} ${second_context_source_registration_id} + + Create Context Source Registration ${first_context_source_registration_payload} + Create Context Source Registration ${second_context_source_registration_payload} + + Set Suite Variable ${first_context_source_registration_id} + Set Suite Variable ${second_context_source_registration_id} + +Delete Created Context Source Registrations + Delete Context Source Registration ${first_context_source_registration_id} + Delete Context Source Registration ${second_context_source_registration_id} diff --git a/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_02.robot b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_02.robot new file mode 100644 index 0000000000000000000000000000000000000000..99bee9233010c3fbe539d6c6039bddc434ec835e --- /dev/null +++ b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_02.robot @@ -0,0 +1,16 @@ +*** Settings *** +Documentation Check that you cannot query context source registrations, if neither Entity types nor Attribute names are provided, an error of type +Resource ${EXECDIR}/resources/ApiUtils.resource +Resource ${EXECDIR}/resources/AssertionUtils.resource +Resource ${EXECDIR}/resources/JsonUtils.resource + +*** Test Case *** +Query Context Source Registrations Without Entity Types and Attribute Names + [Documentation] Check that you cannot query context source registrations, if neither Entity types nor Attribute names are provided, an error of type + [Tags] mandatory + + Query Context Source Registrations context=${ngsild_test_suite_context} + + Check Response Status Code Set To 400 + Check Response Body Containing ProblemDetails Element Containing Type Element set to ${response} ${ERROR_TYPE_BAD_REQUEST_DATA} + Check Response Body Containing ProblemDetails Element Containing Title Element ${response} diff --git a/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_03.robot b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_03.robot new file mode 100644 index 0000000000000000000000000000000000000000..8199b09fd1735135d26ca732c1cf651d8cb8bc51 --- /dev/null +++ b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_03.robot @@ -0,0 +1,25 @@ +*** Settings *** +Documentation Check that you cannot query context source registrations, if the list of Entity identifiers includes a URI which it is not valid, or the query, geo-query or temporal query are not syntactically valid +Resource ${EXECDIR}/resources/ApiUtils.resource +Resource ${EXECDIR}/resources/AssertionUtils.resource +Resource ${EXECDIR}/resources/JsonUtils.resource + +Test Template Query Context Source Registration With Invalid Query Param + +*** Test Cases *** QUERY_PARAM_NAME QUERY_PARAM_VALUE +Invalid URI id invalidUri +Invalid Query q invalidQuery +Invalid GeoQuery georel within +Invalid Temporal Query timerel before + +*** Keywords *** +Query Context Source Registration With Invalid Query Param + [Arguments] ${query_param_name} ${query_param_value} + [Documentation] Check that you cannot query context source registrations, if the list of Entity identifiers includes a URI which it is not valid, or the query, geo-query or temporal query are not syntactically valid + [Tags] mandatory + + Query Context Source Registrations context=${ngsild_test_suite_context} ${query_param_name}=${query_param_value} + + Check Response Status Code Set To 400 + Check Response Body Containing ProblemDetails Element Containing Type Element set to ${response} ${ERROR_TYPE_BAD_REQUEST_DATA} + Check Response Body Containing ProblemDetails Element Containing Title Element ${response} diff --git a/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_04.robot b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_04.robot new file mode 100644 index 0000000000000000000000000000000000000000..78dadc239614ad39713cdfcb8803bc10c3974db6 --- /dev/null +++ b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_04.robot @@ -0,0 +1,36 @@ +*** Settings *** +Documentation Check that you can query context source registrations. If a JSON-LD context is not provided, then all the query terms shall be resolved against the default JSON-LD @context +Resource ${EXECDIR}/resources/ApiUtils.resource +Resource ${EXECDIR}/resources/AssertionUtils.resource +Resource ${EXECDIR}/resources/JsonUtils.resource + +Suite Setup Setup Initial Context Source Registration +Suite Teardown Delete Created Context Source Registration + +*** Variable *** +${context_source_registration_id_prefix}= urn:ngsi-ld:ContextSourceRegistration: +${context_source_registration_payload_file_path}= csourceRegistrations/context-source-registration-sample.jsonld +${expectation_file_path}= csourceRegistrations/expectations/context-source-registrations-037-04-expectation.json + +*** Test Case *** +Query Context Source Registrations Without Context + [Documentation] Check that you can query context source registrations. If a JSON-LD context is not provided, then all the query terms shall be resolved against the default JSON-LD @context + [Tags] mandatory + + Query Context Source Registrations id=${context_source_registration_id} + + @{expected_context_source_registration_ids}= Create List ${context_source_registration_id} + Check Response Status Code Set To 200 + Check Response Body Containing List Containing Context Source Registrations elements ${expectation_file_path} ${expected_context_source_registration_ids} + +*** Keywords *** +Setup Initial Context Source Registration + ${context_source_registration_id}= Generate Random Entity Id ${context_source_registration_id_prefix} + ${context_source_registration_payload}= Load Test Sample ${context_source_registration_payload_file_path} ${context_source_registration_id} + + Create Context Source Registration ${context_source_registration_payload} + + Set Suite Variable ${context_source_registration_id} + +Delete Created Context Source Registration + Delete Context Source Registration ${context_source_registration_id} diff --git a/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_05.robot b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_05.robot new file mode 100644 index 0000000000000000000000000000000000000000..399fa003bfc3ee1b1f31d9807f048079c6ec89d9 --- /dev/null +++ b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_05.robot @@ -0,0 +1,33 @@ +*** Settings *** +Documentation Check that you can query context source registrations matching EntityInfo of RegistrationInfo +Resource ${EXECDIR}/resources/ApiUtils.resource +Resource ${EXECDIR}/resources/AssertionUtils.resource +Resource ${EXECDIR}/resources/JsonUtils.resource + +Test Template Query Context Source Registration Matching EntityInfo of RegistrationInfo + +*** Variable *** +${context_source_registration_id_prefix}= urn:ngsi-ld:ContextSourceRegistration: + +*** Test Cases *** REGISTRATION_FILE_PATH EXPECTATION_FILE_PATH +Registration With EntityInfo Matching The Query csourceRegistrations/context-source-registration-sample.jsonld csourceRegistrations/expectations/context-source-registrations-037-05-01-expectation.json +Registration Without EntityInfo csourceRegistrations/context-source-registration-with-only-properties-information-sample.jsonld csourceRegistrations/expectations/context-source-registrations-037-05-02-expectation.json + +*** Keywords *** +Query Context Source Registration Matching EntityInfo of RegistrationInfo + [Arguments] ${registration_file_path} ${expectation_file_path} + [Documentation] Check that you can query context source registrations matching EntityInfo of RegistrationInfo + [Tags] mandatory + + ${context_source_registration_id}= Generate Random Entity Id ${context_source_registration_id_prefix} + ${context_source_registration_payload}= Load Test Sample ${registration_file_path} ${context_source_registration_id} + Create Context Source Registration ${context_source_registration_payload} + Set Suite Variable ${context_source_registration_id} + + Query Context Source Registrations context=${ngsild_test_suite_context} type=Building attrs=name + + @{expected_context_source_registration_ids}= Create List ${context_source_registration_id} + Check Response Status Code Set To 200 + Check Response Body Containing List Containing Context Source Registrations elements ${expectation_file_path} ${expected_context_source_registration_ids} + + [Teardown] Delete Context Source Registration ${context_source_registration_id} \ No newline at end of file diff --git a/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_06.robot b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_06.robot new file mode 100644 index 0000000000000000000000000000000000000000..97f252914125b2bf9af3f6fb7e5759c8af2e54d4 --- /dev/null +++ b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_06.robot @@ -0,0 +1,41 @@ +*** Settings *** +Documentation Check that you can query context source registrations matching property and relationships names of RegistrationInfo +Resource ${EXECDIR}/resources/ApiUtils.resource +Resource ${EXECDIR}/resources/AssertionUtils.resource +Resource ${EXECDIR}/resources/JsonUtils.resource + +Test Template Query Context Source Registration Matching Properties And Relationships Of RegistrationInfo +Suite Setup Setup Initial Context Source Registration +Suite Teardown Delete Created Context Source Registration + +*** Variable *** +${context_source_registration_id_prefix}= urn:ngsi-ld:ContextSourceRegistration: +${context_source_registration_payload_file_path}= csourceRegistrations/context-source-registration-detailed-information-sample.jsonld + +*** Test Cases *** ATTRS_VALUE EXPECTATION_FILE_PATH +Query With Matching Properties And Relationships name,locatedAt csourceRegistrations/expectations/context-source-registrations-037-06-expectation.json +Query Without Properties And Relationships ${EMPTY} csourceRegistrations/expectations/context-source-registrations-037-06-expectation.json + +*** Keywords *** +Query Context Source Registration Matching Properties And Relationships Of RegistrationInfo + [Arguments] ${attrs_value} ${expectation_file_path} + [Documentation] Check that you can query context source registrations matching property and relationships names of RegistrationInfo + [Tags] mandatory + + Query Context Source Registrations context=${ngsild_test_suite_context} type=Building attrs=${attrs_value} + + @{expected_context_source_registration_ids}= Create List ${context_source_registration_id} + Check Response Status Code Set To 200 + Check Response Body Containing List Containing Context Source Registrations elements ${expectation_file_path} ${expected_context_source_registration_ids} + +*** Keywords *** +Setup Initial Context Source Registration + ${context_source_registration_id}= Generate Random Entity Id ${context_source_registration_id_prefix} + ${context_source_registration_payload}= Load Test Sample ${context_source_registration_payload_file_path} ${context_source_registration_id} + + Create Context Source Registration ${context_source_registration_payload} + + Set Suite Variable ${context_source_registration_id} + +Delete Created Context Source Registration + Delete Context Source Registration ${context_source_registration_id} diff --git a/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_07.robot b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_07.robot new file mode 100644 index 0000000000000000000000000000000000000000..efcd395227ee786b4981640525942d0cbf608527 --- /dev/null +++ b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_07.robot @@ -0,0 +1,41 @@ +*** Settings *** +Documentation Check that you can query context source registrations. If present, the geoquery is matched against the GeoProperty identified in the geoquery +Resource ${EXECDIR}/resources/ApiUtils.resource +Resource ${EXECDIR}/resources/AssertionUtils.resource +Resource ${EXECDIR}/resources/JsonUtils.resource + +Test Template Query Context Source Registration Matching Geoquery +Suite Setup Setup Initial Context Source Registration +Suite Teardown Delete Created Context Source Registration + +*** Variable *** +${context_source_registration_id_prefix}= urn:ngsi-ld:ContextSourceRegistration: +${context_source_registration_payload_file_path}= csourceRegistrations/context-source-registration-location-sample.jsonld +${expectation_file_path}= csourceRegistrations/expectations/context-source-registrations-037-07-expectation.json + +*** Test Cases *** GEOREL GEOMETRY COORDINATES GEOPROPERTY EXPECTATION_FILE_PATH +Near Point near;maxDistance==2000 Point [-8.503,41.202] ${EMPTY} ${expectation_file_path} +Within Polygon within Polygon [[-13.503,47.202],[6.541, 52.961],[20.37,44.653],[9.46,32.57],[-15.23,21.37]] location ${expectation_file_path} + +*** Keywords *** +Query Context Source Registration Matching Geoquery + [Arguments] ${georel} ${geometry} ${coordinates} ${geoproperty} ${expectation_file_path} + [Documentation] Check that you can query context source registrations. If present, the geoquery is matched against the GeoProperty identified in the geoquery + [Tags] mandatory + + Query Context Source Registrations context=${ngsild_test_suite_context} type=Building georel=${georel} geometry=${geometry} coordinates=${coordinates} geoproperty=${geoproperty} + + @{expected_context_source_registration_ids}= Create List ${context_source_registration_id} + Check Response Status Code Set To 200 + Check Response Body Containing List Containing Context Source Registrations elements ${expectation_file_path} ${expected_context_source_registration_ids} + +Setup Initial Context Source Registration + ${context_source_registration_id}= Generate Random Entity Id ${context_source_registration_id_prefix} + ${context_source_registration_payload}= Load Test Sample ${context_source_registration_payload_file_path} ${context_source_registration_id} + + Create Context Source Registration ${context_source_registration_payload} + + Set Suite Variable ${context_source_registration_id} + +Delete Created Context Source Registration + Delete Context Source Registration ${context_source_registration_id} diff --git a/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_08.robot b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_08.robot new file mode 100644 index 0000000000000000000000000000000000000000..256d33933572d2c062625f59727834eb4f6ae1b5 --- /dev/null +++ b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_08.robot @@ -0,0 +1,36 @@ +*** Settings *** +Documentation Check that you can query context source registrations. If no temporal query is present, only Context Source Registrations for Context Sources providing latest information are considered +Resource ${EXECDIR}/resources/ApiUtils.resource +Resource ${EXECDIR}/resources/AssertionUtils.resource +Resource ${EXECDIR}/resources/JsonUtils.resource + +Suite Setup Setup Initial Context Source Registration +Suite Teardown Delete Created Context Source Registration + +*** Variable *** +${context_source_registration_id_prefix}= urn:ngsi-ld:ContextSourceRegistration: +${context_source_registration_payload_file_path}= csourceRegistrations/context-source-registration-sample.jsonld +${expectation_file_path}= csourceRegistrations/expectations/context-source-registrations-037-08-expectation.json + +*** Test Case *** +Query Context Source Registration Without Temporal Query + [Documentation] Check that you can query context source registrations. If no temporal query is present, only Context Source Registrations for Context Sources providing latest information are considered + [Tags] mandatory + + Query Context Source Registrations context=${ngsild_test_suite_context} type=Building + + @{expected_context_source_registration_ids}= Create List ${context_source_registration_id} + Check Response Status Code Set To 200 + Check Response Body Containing List Containing Context Source Registrations elements ${expectation_file_path} ${expected_context_source_registration_ids} + +*** Keywords *** +Setup Initial Context Source Registration + ${context_source_registration_id}= Generate Random Entity Id ${context_source_registration_id_prefix} + ${context_source_registration_payload}= Load Test Sample ${context_source_registration_payload_file_path} ${context_source_registration_id} + + Create Context Source Registration ${context_source_registration_payload} + + Set Suite Variable ${context_source_registration_id} + +Delete Created Context Source Registration + Delete Context Source Registration ${context_source_registration_id} diff --git a/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_09.robot b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_09.robot new file mode 100644 index 0000000000000000000000000000000000000000..8b0f1f10d81d8a1e315ace27ad8dcacd757aaddb --- /dev/null +++ b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_09.robot @@ -0,0 +1,39 @@ +*** Settings *** +Documentation Check that you can query context source registrations. If present, the temporal query is matched against the observationInterval or the managementInterval +Resource ${EXECDIR}/resources/ApiUtils.resource +Resource ${EXECDIR}/resources/AssertionUtils.resource +Resource ${EXECDIR}/resources/JsonUtils.resource + +Test Template Query Context Source Registration Matching Temporal Query + +*** Variable *** +${context_source_registration_id_prefix}= urn:ngsi-ld:ContextSourceRegistration: +${context_source_registration_observation_interval_payload_file_path}= csourceRegistrations/context-source-registration-observationInterval-sample.jsonld +${context_source_registration_management_interval_payload_file_path}= csourceRegistrations/context-source-registration-managementInterval-sample.jsonld +${observation_interval_expectation_file_path}= csourceRegistrations/expectations/context-source-registrations-037-09-01-expectation.json +${management_interval_expectation_file_path}= csourceRegistrations/expectations/context-source-registrations-037-09-02-expectation.json + +*** Test Cases *** PAYLOAD_FILE_PATH TIMEPROPERTY EXPECTATION_FILE_PATH +Observation Interval With observedAt ${context_source_registration_observation_interval_payload_file_path} observedAt ${observation_interval_expectation_file_path} +Observation Interval Without timeproperty ${context_source_registration_observation_interval_payload_file_path} ${EMPTY} ${observation_interval_expectation_file_path} +Mqnagement Interval With createdAt ${context_source_registration_management_interval_payload_file_path} createdAt ${management_interval_expectation_file_path} +Mqnagement Interval With modifiedAt ${context_source_registration_management_interval_payload_file_path} modifiedAt ${management_interval_expectation_file_path} + +*** Keywords *** +Query Context Source Registration Matching Temporal Query + [Arguments] ${payload_file_path} ${timeproperty} ${expectation_file_path} + [Documentation] Check that you can query context source registrations. If present, the temporal query is matched against the observationInterval or the managementInterval + [Tags] mandatory + + ${context_source_registration_id}= Generate Random Entity Id ${context_source_registration_id_prefix} + ${context_source_registration_payload}= Load Test Sample ${payload_file_path} ${context_source_registration_id} + Create Context Source Registration ${context_source_registration_payload} + Set Suite Variable ${context_source_registration_id} + + Query Context Source Registrations context=${ngsild_test_suite_context} type=Building timeproperty=${timeproperty} timerel=before timeAt=2021-08-01T22:00:00Z + + @{expected_context_source_registration_ids}= Create List ${context_source_registration_id} + Check Response Status Code Set To 200 + Check Response Body Containing List Containing Context Source Registrations elements ${expectation_file_path} ${expected_context_source_registration_ids} + + [Teardown] Delete Context Source Registration ${context_source_registration_id} diff --git a/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_11.robot b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_11.robot new file mode 100644 index 0000000000000000000000000000000000000000..ee83a7d74c01b91bf169f184e2dac0e3bbe15706 --- /dev/null +++ b/TP/NGSI-LD/ContextSource/Registration/QueryContextSourceRegistrations/037_11.robot @@ -0,0 +1,53 @@ +*** Settings *** +Documentation Check that you can query context source registrations with providing page and limit parameters, pagination logic shall be in place as mandated by clause 5.5.9. +Resource ${EXECDIR}/resources/ApiUtils.resource +Resource ${EXECDIR}/resources/AssertionUtils.resource +Resource ${EXECDIR}/resources/JsonUtils.resource + +Test Template Query Context Source Registration With Limit And Page Parameters +Suite Setup Setup Initial Context Source Registrations +Suite Teardown Delete Created Context Source Registrations + +*** Variable *** +${context_source_registration_id_prefix}= urn:ngsi-ld:ContextSourceRegistration: +${first_context_source_registration_payload_file_path}= csourceRegistrations/context-source-registration-sample.jsonld +${second_context_source_registration_payload_file_path}= csourceRegistrations/context-source-registration-location-sample.jsonld +${third_context_source_registration_payload_file_path}= csourceRegistrations/context-source-registration-detailed-information-sample.jsonld + +*** Test Cases *** LIMIT PAGE EXPECTED_NUMBER PREV_LINK NEXT_LINK +Query Second Subscription ${1} ${2} ${1} ;rel="prev";type="application/ld+json" ;rel="next";type="application/ld+json" +Query Last Subscription ${2} ${2} ${1} ;rel="prev";type="application/ld+json" ${EMPTY} +Query All Subscriptions ${15} ${1} ${3} ${EMPTY} ${EMPTY} + +*** Keywords *** +Query Context Source Registration With Limit And Page Parameters + [Arguments] ${limit} ${page} ${expected_number} ${prev_link} ${next_link} + [Documentation] Check that you can query context source registrations with providing page and limit parameters, pagination logic shall be in place as mandated by clause 5.5.9. + [Tags] mandatory + + Query Context Source Registrations context=${ngsild_test_suite_context} type=Building limit=${limit} page=${page} + + Check Response Status Code Set To 200 + Check Response Body Containing Number Of Entities ContextSourceRegistration ${expected_number} + Check Pagination Prev And Next Headers ${prev_link} ${next_link} + +Setup Initial Context Source Registrations + ${first_context_source_registration_id}= Generate Random Entity Id ${context_source_registration_id_prefix} + ${second_context_source_registration_id}= Generate Random Entity Id ${context_source_registration_id_prefix} + ${third_context_source_registration_id}= Generate Random Entity Id ${context_source_registration_id_prefix} + ${first_context_source_registration_payload}= Load Test Sample ${first_context_source_registration_payload_file_path} ${first_context_source_registration_id} + ${second_context_source_registration_payload}= Load Test Sample ${second_context_source_registration_payload_file_path} ${second_context_source_registration_id} + ${third_context_source_registration_payload}= Load Test Sample ${third_context_source_registration_payload_file_path} ${third_context_source_registration_id} + + Create Context Source Registration ${first_context_source_registration_payload} + Create Context Source Registration ${second_context_source_registration_payload} + Create Context Source Registration ${third_context_source_registration_payload} + + Set Suite Variable ${first_context_source_registration_id} + Set Suite Variable ${second_context_source_registration_id} + Set Suite Variable ${third_context_source_registration_id} + +Delete Created Context Source Registrations + Delete Context Source Registration ${first_context_source_registration_id} + Delete Context Source Registration ${second_context_source_registration_id} + Delete Context Source Registration ${third_context_source_registration_id} diff --git a/data/csourceRegistrations/context-source-registration-with-only-properties-information-sample.jsonld b/data/csourceRegistrations/context-source-registration-with-only-properties-information-sample.jsonld new file mode 100644 index 0000000000000000000000000000000000000000..71583c3577e35b1efcd36a4dbd94e8be8e0f5a03 --- /dev/null +++ b/data/csourceRegistrations/context-source-registration-with-only-properties-information-sample.jsonld @@ -0,0 +1,14 @@ +{ + "id":"urn:ngsi-ld:ContextSourceRegistration:randomUUID", + "type":"ContextSourceRegistration", + "information":[ + { + "propertyNames":["name", "subCategory"] + } + ], + "endpoint":"http://my.csource.org:1026", + "@context":[ + "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/feature/add-json-ld-context-for-ngsi-ld-test-suite/ngsi-ld-test-suite/ngsi-ld-test-suite-context.jsonld", + "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" + ] +} \ No newline at end of file diff --git a/data/csourceRegistrations/expectations/context-source-registrations-037-01-expectation.json b/data/csourceRegistrations/expectations/context-source-registrations-037-01-expectation.json new file mode 100644 index 0000000000000000000000000000000000000000..b8d0242e3ecdc250d2797b0dbda9dd6d18d54bb5 --- /dev/null +++ b/data/csourceRegistrations/expectations/context-source-registrations-037-01-expectation.json @@ -0,0 +1,37 @@ +[ + { + "id":"urn:ngsi-ld:ContextSourceRegistration:randomUUID", + "type":"ContextSourceRegistration", + "information":[ + { + "entities":[ + { + "type":"Building" + } + ] + } + ], + "endpoint":"http://my.csource.org:1026" + }, + { + "id":"urn:ngsi-ld:ContextSourceRegistration:randomUUID", + "type":"ContextSourceRegistration", + "information":[ + { + "entities":[ + { + "type":"Building" + } + ], + "propertyNames":[ + "name", + "subCategory" + ], + "relationshipNames":[ + "locatedAt" + ] + } + ], + "endpoint":"http://my.csource.org:1026" + } +] \ No newline at end of file diff --git a/data/csourceRegistrations/expectations/context-source-registrations-037-04-expectation.json b/data/csourceRegistrations/expectations/context-source-registrations-037-04-expectation.json new file mode 100644 index 0000000000000000000000000000000000000000..46ec3a76fcf8089aceb1bed258e497617b8a2038 --- /dev/null +++ b/data/csourceRegistrations/expectations/context-source-registrations-037-04-expectation.json @@ -0,0 +1,16 @@ +[ + { + "id":"urn:ngsi-ld:ContextSourceRegistration:randomUUID", + "type":"ContextSourceRegistration", + "information":[ + { + "entities":[ + { + "type":"https://ngsi-ld-test-suite/context#Building" + } + ] + } + ], + "endpoint":"http://my.csource.org:1026" + } +] \ No newline at end of file diff --git a/data/csourceRegistrations/expectations/context-source-registrations-037-05-01-expectation.json b/data/csourceRegistrations/expectations/context-source-registrations-037-05-01-expectation.json new file mode 100644 index 0000000000000000000000000000000000000000..0ddd335ca69f8484aa28852e8b385235cb7b08d3 --- /dev/null +++ b/data/csourceRegistrations/expectations/context-source-registrations-037-05-01-expectation.json @@ -0,0 +1,16 @@ +[ + { + "id":"urn:ngsi-ld:ContextSourceRegistration:randomUUID", + "type":"ContextSourceRegistration", + "information":[ + { + "entities":[ + { + "type":"Building" + } + ] + } + ], + "endpoint":"http://my.csource.org:1026" + } +] \ No newline at end of file diff --git a/data/csourceRegistrations/expectations/context-source-registrations-037-05-02-expectation.json b/data/csourceRegistrations/expectations/context-source-registrations-037-05-02-expectation.json new file mode 100644 index 0000000000000000000000000000000000000000..51faeabc320ba3587dcef25713c91c85ca763e02 --- /dev/null +++ b/data/csourceRegistrations/expectations/context-source-registrations-037-05-02-expectation.json @@ -0,0 +1,12 @@ +[ + { + "id":"urn:ngsi-ld:ContextSourceRegistration:randomUUID", + "type":"ContextSourceRegistration", + "information":[ + { + "propertyNames":["name", "subCategory"] + } + ], + "endpoint":"http://my.csource.org:1026" + } +] \ No newline at end of file diff --git a/data/csourceRegistrations/expectations/context-source-registrations-037-06-expectation.json b/data/csourceRegistrations/expectations/context-source-registrations-037-06-expectation.json new file mode 100644 index 0000000000000000000000000000000000000000..baa1c66e89192e287d213340e4e65cc47c91d497 --- /dev/null +++ b/data/csourceRegistrations/expectations/context-source-registrations-037-06-expectation.json @@ -0,0 +1,18 @@ +[ + { + "id":"urn:ngsi-ld:ContextSourceRegistration:randomUUID", + "type":"ContextSourceRegistration", + "information":[ + { + "entities":[ + { + "type":"Building" + } + ], + "propertyNames":["name", "subCategory"], + "relationshipNames":["locatedAt"] + } + ], + "endpoint":"http://my.csource.org:1026" + } +] \ No newline at end of file diff --git a/data/csourceRegistrations/expectations/context-source-registrations-037-07-expectation.json b/data/csourceRegistrations/expectations/context-source-registrations-037-07-expectation.json new file mode 100644 index 0000000000000000000000000000000000000000..51487e5aab3a43d87d7424d531f493e14ba4041f --- /dev/null +++ b/data/csourceRegistrations/expectations/context-source-registrations-037-07-expectation.json @@ -0,0 +1,26 @@ +[ + { + "id":"urn:ngsi-ld:ContextSourceRegistration:randomUUID", + "type":"ContextSourceRegistration", + "information":[ + { + "entities":[ + { + "type":"Building" + } + ] + } + ], + "location":{ + "type":"GeoProperty", + "value":{ + "type":"Point", + "coordinates":[ + -8.521, + 41.2 + ] + } + }, + "endpoint":"http://my.csource.org:1026" + } +] \ No newline at end of file diff --git a/data/csourceRegistrations/expectations/context-source-registrations-037-08-expectation.json b/data/csourceRegistrations/expectations/context-source-registrations-037-08-expectation.json new file mode 100644 index 0000000000000000000000000000000000000000..0ddd335ca69f8484aa28852e8b385235cb7b08d3 --- /dev/null +++ b/data/csourceRegistrations/expectations/context-source-registrations-037-08-expectation.json @@ -0,0 +1,16 @@ +[ + { + "id":"urn:ngsi-ld:ContextSourceRegistration:randomUUID", + "type":"ContextSourceRegistration", + "information":[ + { + "entities":[ + { + "type":"Building" + } + ] + } + ], + "endpoint":"http://my.csource.org:1026" + } +] \ No newline at end of file diff --git a/data/csourceRegistrations/expectations/context-source-registrations-037-09-01-expectation.json b/data/csourceRegistrations/expectations/context-source-registrations-037-09-01-expectation.json new file mode 100644 index 0000000000000000000000000000000000000000..1312004a07dd06e52f1d72ea100629a7a175b640 --- /dev/null +++ b/data/csourceRegistrations/expectations/context-source-registrations-037-09-01-expectation.json @@ -0,0 +1,20 @@ +[ + { + "id":"urn:ngsi-ld:ContextSourceRegistration:randomUUID", + "type":"ContextSourceRegistration", + "information":[ + { + "entities":[ + { + "type":"Building" + } + ] + } + ], + "observationInterval":{ + "startAt":"2020-08-01T22:07:00Z", + "endAt":"2021-08-01T21:07:00Z" + }, + "endpoint":"http://my.csource.org:1026" + } +] \ No newline at end of file diff --git a/data/csourceRegistrations/expectations/context-source-registrations-037-09-02-expectation.json b/data/csourceRegistrations/expectations/context-source-registrations-037-09-02-expectation.json new file mode 100644 index 0000000000000000000000000000000000000000..cb0ab1fe6188db83e637bd898c25dbc7c7f1987e --- /dev/null +++ b/data/csourceRegistrations/expectations/context-source-registrations-037-09-02-expectation.json @@ -0,0 +1,20 @@ +[ + { + "id":"urn:ngsi-ld:ContextSourceRegistration:randomUUID", + "type":"ContextSourceRegistration", + "information":[ + { + "entities":[ + { + "type":"Building" + } + ] + } + ], + "managementInterval":{ + "startAt":"2020-08-01T22:07:00Z", + "endAt":"2021-08-01T21:07:00Z" + }, + "endpoint":"http://my.csource.org:1026" + } +] \ No newline at end of file diff --git a/resources/ApiUtils.resource b/resources/ApiUtils.resource index 6246a66629811d9a92a1c7a0b173c0104c9282d0..15cc86db2945dbe232617cdcac2a9f46bcd47544 100755 --- a/resources/ApiUtils.resource +++ b/resources/ApiUtils.resource @@ -431,6 +431,32 @@ Update Context Source Registration Set Test Variable ${response} +Query Context Source Registrations + [Arguments] ${context}=${EMPTY} ${id}=${EMPTY} ${type}=${EMPTY} ${attrs}=${EMPTY} ${q}=${EMPTY} ${georel}=${EMPTY} ${geometry}=${EMPTY} ${coordinates}=${EMPTY} ${geoproperty}=${EMPTY} ${timeproperty}=${EMPTY} ${timerel}=${EMPTY} ${timeAt}=${EMPTY} ${limit}=${EMPTY} ${page}=${EMPTY} + + &{headers}= Create Dictionary + &{params}= Create Dictionary + Run Keyword If '${context}'!='' Set To Dictionary ${headers} Link=<${context}>; rel="http://www.w3.org/ns/json-ld#context";type="application/ld+json" + Run Keyword If '${id}'!='' Set To Dictionary ${params} id=${id} + Run Keyword If '${type}'!='' Set To Dictionary ${params} type=${type} + Run Keyword If '${attrs}'!='' Set To Dictionary ${params} attrs=${attrs} + Run Keyword If '${q}'!='' Set To Dictionary ${params} q=${q} + Run Keyword If '${georel}'!='' Set To Dictionary ${params} georel=${georel} + Run Keyword If '${geometry}'!='' Set To Dictionary ${params} geometry=${geometry} + Run Keyword If '${coordinates}'!='' Set To Dictionary ${params} coordinates=${coordinates} + Run Keyword If '${geoproperty}'!='' Set To Dictionary ${params} geoproperty=${geoproperty} + Run Keyword If '${timeproperty}'!='' Set To Dictionary ${params} timeproperty=${timeproperty} + Run Keyword If '${timerel}'!='' Set To Dictionary ${params} timerel=${timerel} + Run Keyword If '${timeAt}'!='' Set To Dictionary ${params} timeAt=${timeAt} + Run Keyword If '${limit}'!='' Set To Dictionary ${params} limit=${limit} + Run Keyword If '${page}'!='' Set To Dictionary ${params} page=${page} + + ${response}= GET ${CONTEXT_SOURCE_REGISTRATION_ENDPOINT_PATH} headers=${headers} query=${params} + Output request + Output response + + Set Test Variable ${response} + Delete Context Source Registration [Arguments] ${context_source_registration_id} diff --git a/resources/AssertionUtils.resource b/resources/AssertionUtils.resource index 2116fcfbe9b5597b1b4b8d8862c380798921cbdd..c75596b9723d59f4a76335befeea1ff42f6ef852 100755 --- a/resources/AssertionUtils.resource +++ b/resources/AssertionUtils.resource @@ -194,6 +194,17 @@ Check Response Body Containing Attribute element ${comparison_result}= Compare Dictionaries Ignoring Keys ${response['body']} ${attribute_payload} ${ignored_keys} Should Be True ${comparison_result} msg=Attribute Comparison Failed +Check Response Body Containing List Containing Context Source Registrations elements + [Arguments] ${expectation_file_path} ${expected_context_source_registrations_ids} + ${expected_context_source_registrations_payload}= Load Json From File ${EXECDIR}/data/${expectation_file_path} + ${index}= Set Variable 0 + FOR ${expected_context_source_registration_id} IN @{expected_context_source_registrations_ids} + ${expected_context_source_registrations_payload}= Update Value To Json ${expected_context_source_registrations_payload} $.[${index}]..id ${expected_context_source_registration_id} + ${index}= Evaluate ${index} + 1 + END + ${comparaison_result}= Compare Dictionaries Ignoring Keys ${response['body']} ${expected_context_source_registrations_payload} ${EMPTY} + Should Be True ${comparaison_result} msg=Context Source Registration Comparaison Failed + Check Response Body Type When Using Session Request [Arguments] ${response} ${type} Should Be Equal ${response['type']} ${type} diff --git a/robot/bin/activate b/robot/bin/activate new file mode 100644 index 0000000000000000000000000000000000000000..20ba13d8e4d392885aeaf8cbeca83650628862c8 --- /dev/null +++ b/robot/bin/activate @@ -0,0 +1,84 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + + +if [ "${BASH_SOURCE-}" = "$0" ]; then + echo "You must source this script: \$ source $0" >&2 + exit 33 +fi + +deactivate () { + unset -f pydoc >/dev/null 2>&1 + + # reset old environment variables + # ! [ -z ${VAR+_} ] returns true if VAR is declared at all + if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then + PATH="$_OLD_VIRTUAL_PATH" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then + PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ] ; then + hash -r 2>/dev/null + fi + + if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then + PS1="$_OLD_VIRTUAL_PS1" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + if [ ! "${1-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV='/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot' +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/bin:$PATH" +export PATH + +# unset PYTHONHOME if set +if ! [ -z "${PYTHONHOME+_}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1-}" + if [ "x" != x ] ; then + PS1="${PS1-}" + else + PS1="(`basename \"$VIRTUAL_ENV\"`) ${PS1-}" + fi + export PS1 +fi + +# Make sure to unalias pydoc if it's already there +alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true + +pydoc () { + python -m pydoc "$@" +} + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ] ; then + hash -r 2>/dev/null +fi diff --git a/robot/bin/activate.csh b/robot/bin/activate.csh new file mode 100644 index 0000000000000000000000000000000000000000..278c109309baf943b61c07311ad9dc5583f16bef --- /dev/null +++ b/robot/bin/activate.csh @@ -0,0 +1,55 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. +# Created by Davide Di Blasi . + +set newline='\ +' + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH:q" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT:q" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate && unalias pydoc' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV '/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot' + +set _OLD_VIRTUAL_PATH="$PATH:q" +setenv PATH "$VIRTUAL_ENV:q/bin:$PATH:q" + + + +if ('' != "") then + set env_name = '' +else + set env_name = '('"$VIRTUAL_ENV:t:q"') ' +endif + +if ( $?VIRTUAL_ENV_DISABLE_PROMPT ) then + if ( $VIRTUAL_ENV_DISABLE_PROMPT == "" ) then + set do_prompt = "1" + else + set do_prompt = "0" + endif +else + set do_prompt = "1" +endif + +if ( $do_prompt == "1" ) then + # Could be in a non-interactive environment, + # in which case, $prompt is undefined and we wouldn't + # care about the prompt anyway. + if ( $?prompt ) then + set _OLD_VIRTUAL_PROMPT="$prompt:q" + if ( "$prompt:q" =~ *"$newline:q"* ) then + : + else + set prompt = "$env_name:q$prompt:q" + endif + endif +endif + +unset env_name +unset do_prompt + +alias pydoc python -m pydoc + +rehash diff --git a/robot/bin/activate.fish b/robot/bin/activate.fish new file mode 100644 index 0000000000000000000000000000000000000000..aeafac6f93adbf05c5d26ebeff7dc97dcc5419da --- /dev/null +++ b/robot/bin/activate.fish @@ -0,0 +1,100 @@ +# This file must be used using `source bin/activate.fish` *within a running fish ( http://fishshell.com ) session*. +# Do not run it directly. + +function _bashify_path -d "Converts a fish path to something bash can recognize" + set fishy_path $argv + set bashy_path $fishy_path[1] + for path_part in $fishy_path[2..-1] + set bashy_path "$bashy_path:$path_part" + end + echo $bashy_path +end + +function _fishify_path -d "Converts a bash path to something fish can recognize" + echo $argv | tr ':' '\n' +end + +function deactivate -d 'Exit virtualenv mode and return to the normal environment.' + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + # https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling + if test (echo $FISH_VERSION | head -c 1) -lt 3 + set -gx PATH (_fishify_path "$_OLD_VIRTUAL_PATH") + else + set -gx PATH "$_OLD_VIRTUAL_PATH" + end + set -e _OLD_VIRTUAL_PATH + end + + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME "$_OLD_VIRTUAL_PYTHONHOME" + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + and functions -q _old_fish_prompt + # Set an empty local `$fish_function_path` to allow the removal of `fish_prompt` using `functions -e`. + set -l fish_function_path + + # Erase virtualenv's `fish_prompt` and restore the original. + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + set -e _OLD_FISH_PROMPT_OVERRIDE + end + + set -e VIRTUAL_ENV + + if test "$argv[1]" != 'nondestructive' + # Self-destruct! + functions -e pydoc + functions -e deactivate + functions -e _bashify_path + functions -e _fishify_path + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV '/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot' + +# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling +if test (echo $FISH_VERSION | head -c 1) -lt 3 + set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH) +else + set -gx _OLD_VIRTUAL_PATH "$PATH" +end +set -gx PATH "$VIRTUAL_ENV"'/bin' $PATH + +# Unset `$PYTHONHOME` if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +function pydoc + python -m pydoc $argv +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # Copy the current `fish_prompt` function as `_old_fish_prompt`. + functions -c fish_prompt _old_fish_prompt + + function fish_prompt + # Run the user's prompt first; it might depend on (pipe)status. + set -l prompt (_old_fish_prompt) + + # Prompt override provided? + # If not, just prepend the environment name. + if test -n '' + printf '%s%s' '' (set_color normal) + else + printf '%s(%s) ' (set_color normal) (basename "$VIRTUAL_ENV") + end + + string join -- \n $prompt # handle multi-line prompts + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" +end diff --git a/robot/bin/activate.ps1 b/robot/bin/activate.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..95504d3956b326b1ae2ece60f5db082db8d8bf99 --- /dev/null +++ b/robot/bin/activate.ps1 @@ -0,0 +1,60 @@ +$script:THIS_PATH = $myinvocation.mycommand.path +$script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent + +function global:deactivate([switch] $NonDestructive) { + if (Test-Path variable:_OLD_VIRTUAL_PATH) { + $env:PATH = $variable:_OLD_VIRTUAL_PATH + Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global + } + + if (Test-Path function:_old_virtual_prompt) { + $function:prompt = $function:_old_virtual_prompt + Remove-Item function:\_old_virtual_prompt + } + + if ($env:VIRTUAL_ENV) { + Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue + } + + if (!$NonDestructive) { + # Self destruct! + Remove-Item function:deactivate + Remove-Item function:pydoc + } +} + +function global:pydoc { + python -m pydoc $args +} + +# unset irrelevant variables +deactivate -nondestructive + +$VIRTUAL_ENV = $BASE_DIR +$env:VIRTUAL_ENV = $VIRTUAL_ENV + +New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH + +$env:PATH = "$env:VIRTUAL_ENV/bin:" + $env:PATH +if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) { + function global:_old_virtual_prompt { + "" + } + $function:_old_virtual_prompt = $function:prompt + + if ("" -ne "") { + function global:prompt { + # Add the custom prefix to the existing prompt + $previous_prompt_value = & $function:_old_virtual_prompt + ("" + $previous_prompt_value) + } + } + else { + function global:prompt { + # Add a prefix to the current prompt, but don't discard it. + $previous_prompt_value = & $function:_old_virtual_prompt + $new_prompt_value = "($( Split-Path $env:VIRTUAL_ENV -Leaf )) " + ($new_prompt_value + $previous_prompt_value) + } + } +} diff --git a/robot/bin/activate.xsh b/robot/bin/activate.xsh new file mode 100644 index 0000000000000000000000000000000000000000..5997c9853385b946a183bd1498820f7bee1d483d --- /dev/null +++ b/robot/bin/activate.xsh @@ -0,0 +1,46 @@ +"""Xonsh activate script for virtualenv""" +from xonsh.tools import get_sep as _get_sep + +def _deactivate(args): + if "pydoc" in aliases: + del aliases["pydoc"] + + if ${...}.get("_OLD_VIRTUAL_PATH", ""): + $PATH = $_OLD_VIRTUAL_PATH + del $_OLD_VIRTUAL_PATH + + if ${...}.get("_OLD_VIRTUAL_PYTHONHOME", ""): + $PYTHONHOME = $_OLD_VIRTUAL_PYTHONHOME + del $_OLD_VIRTUAL_PYTHONHOME + + if "VIRTUAL_ENV" in ${...}: + del $VIRTUAL_ENV + + if "VIRTUAL_ENV_PROMPT" in ${...}: + del $VIRTUAL_ENV_PROMPT + + if "nondestructive" not in args: + # Self destruct! + del aliases["deactivate"] + + +# unset irrelevant variables +_deactivate(["nondestructive"]) +aliases["deactivate"] = _deactivate + +$VIRTUAL_ENV = r"/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot" + +$_OLD_VIRTUAL_PATH = $PATH +$PATH = $PATH[:] +$PATH.add($VIRTUAL_ENV + _get_sep() + "bin", front=True, replace=True) + +if ${...}.get("PYTHONHOME", ""): + # unset PYTHONHOME if set + $_OLD_VIRTUAL_PYTHONHOME = $PYTHONHOME + del $PYTHONHOME + +$VIRTUAL_ENV_PROMPT = "" +if not $VIRTUAL_ENV_PROMPT: + del $VIRTUAL_ENV_PROMPT + +aliases["pydoc"] = ["python", "-m", "pydoc"] diff --git a/robot/bin/activate_this.py b/robot/bin/activate_this.py new file mode 100644 index 0000000000000000000000000000000000000000..44799869857f0fc68aef1c85a38fdbf236f3b017 --- /dev/null +++ b/robot/bin/activate_this.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +"""Activate virtualenv for current interpreter: + +Use exec(open(this_file).read(), {'__file__': this_file}). + +This can be used when you must use an existing Python interpreter, not the virtualenv bin/python. +""" +import os +import site +import sys + +try: + abs_file = os.path.abspath(__file__) +except NameError: + raise AssertionError("You must use exec(open(this_file).read(), {'__file__': this_file}))") + +bin_dir = os.path.dirname(abs_file) +base = bin_dir[: -len("bin") - 1] # strip away the bin part from the __file__, plus the path separator + +# prepend bin to PATH (this file is inside the bin directory) +os.environ["PATH"] = os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep)) +os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory + +# add the virtual environments libraries to the host python import mechanism +prev_length = len(sys.path) +for lib in "../lib/python3.8/site-packages".split(os.pathsep): + path = os.path.realpath(os.path.join(bin_dir, lib)) + site.addsitedir(path.decode("utf-8") if "" else path) +sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length] + +sys.real_prefix = sys.prefix +sys.prefix = base diff --git a/robot/bin/chardetect b/robot/bin/chardetect new file mode 100755 index 0000000000000000000000000000000000000000..7ba5283b977f67c08a2b7d5fcd23caacbdb563ad --- /dev/null +++ b/robot/bin/chardetect @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from chardet.cli.chardetect import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/chardetect-3.8 b/robot/bin/chardetect-3.8 new file mode 100755 index 0000000000000000000000000000000000000000..7ba5283b977f67c08a2b7d5fcd23caacbdb563ad --- /dev/null +++ b/robot/bin/chardetect-3.8 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from chardet.cli.chardetect import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/chardetect3 b/robot/bin/chardetect3 new file mode 100755 index 0000000000000000000000000000000000000000..7ba5283b977f67c08a2b7d5fcd23caacbdb563ad --- /dev/null +++ b/robot/bin/chardetect3 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from chardet.cli.chardetect import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/coverage b/robot/bin/coverage new file mode 100755 index 0000000000000000000000000000000000000000..a06c93bb951a46144d99c1b3cbf7b514b72a6504 --- /dev/null +++ b/robot/bin/coverage @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from coverage.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/coverage-3.8 b/robot/bin/coverage-3.8 new file mode 100755 index 0000000000000000000000000000000000000000..a06c93bb951a46144d99c1b3cbf7b514b72a6504 --- /dev/null +++ b/robot/bin/coverage-3.8 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from coverage.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/coverage3 b/robot/bin/coverage3 new file mode 100755 index 0000000000000000000000000000000000000000..a06c93bb951a46144d99c1b3cbf7b514b72a6504 --- /dev/null +++ b/robot/bin/coverage3 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from coverage.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/deep b/robot/bin/deep new file mode 100755 index 0000000000000000000000000000000000000000..37e05e1992182e79e79a8fd642d470cfa91ab557 --- /dev/null +++ b/robot/bin/deep @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from deepdiff.commands import cli +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cli()) diff --git a/robot/bin/distro b/robot/bin/distro new file mode 100755 index 0000000000000000000000000000000000000000..6009396d6f96fc2950ec5df246d2843502d89843 --- /dev/null +++ b/robot/bin/distro @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from distro import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/distro-3.8 b/robot/bin/distro-3.8 new file mode 100755 index 0000000000000000000000000000000000000000..6009396d6f96fc2950ec5df246d2843502d89843 --- /dev/null +++ b/robot/bin/distro-3.8 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from distro import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/distro3 b/robot/bin/distro3 new file mode 100755 index 0000000000000000000000000000000000000000..6009396d6f96fc2950ec5df246d2843502d89843 --- /dev/null +++ b/robot/bin/distro3 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from distro import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/easy_install b/robot/bin/easy_install new file mode 100755 index 0000000000000000000000000000000000000000..0fdfaa472538ebcd8f5490b34e499b951082b332 --- /dev/null +++ b/robot/bin/easy_install @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from setuptools.command.easy_install import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/easy_install-3.8 b/robot/bin/easy_install-3.8 new file mode 100755 index 0000000000000000000000000000000000000000..0fdfaa472538ebcd8f5490b34e499b951082b332 --- /dev/null +++ b/robot/bin/easy_install-3.8 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from setuptools.command.easy_install import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/easy_install3 b/robot/bin/easy_install3 new file mode 100755 index 0000000000000000000000000000000000000000..0fdfaa472538ebcd8f5490b34e499b951082b332 --- /dev/null +++ b/robot/bin/easy_install3 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from setuptools.command.easy_install import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/genson b/robot/bin/genson new file mode 100755 index 0000000000000000000000000000000000000000..ad875e542e5d8a6d7eed7f9c03b3b8c66c0aee4f --- /dev/null +++ b/robot/bin/genson @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from genson.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/jsonpath.py b/robot/bin/jsonpath.py new file mode 100755 index 0000000000000000000000000000000000000000..3355364f3af69f63dea2dbdbd8360cb510322aac --- /dev/null +++ b/robot/bin/jsonpath.py @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from jsonpath_rw.bin.jsonpath import entry_point +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(entry_point()) diff --git a/robot/bin/jsonpath_ng b/robot/bin/jsonpath_ng new file mode 100755 index 0000000000000000000000000000000000000000..20857f3251c486faac092b53e053b40daa2e76dc --- /dev/null +++ b/robot/bin/jsonpath_ng @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from jsonpath_ng.bin.jsonpath import entry_point +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(entry_point()) diff --git a/robot/bin/jsonpointer b/robot/bin/jsonpointer new file mode 100755 index 0000000000000000000000000000000000000000..2e2891e42f9c92fb2a7daab863ba59a0da289a1e --- /dev/null +++ b/robot/bin/jsonpointer @@ -0,0 +1,69 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- + +from __future__ import print_function + +import sys +import os.path +import json +import jsonpointer +import argparse + + +parser = argparse.ArgumentParser( + description='Resolve a JSON pointer on JSON files') + +# Accept pointer as argument or as file +ptr_group = parser.add_mutually_exclusive_group(required=True) + +ptr_group.add_argument('-f', '--pointer-file', type=argparse.FileType('r'), + nargs='?', + help='File containing a JSON pointer expression') + +ptr_group.add_argument('POINTER', type=str, nargs='?', + help='A JSON pointer expression') + +parser.add_argument('FILE', type=argparse.FileType('r'), nargs='+', + help='Files for which the pointer should be resolved') +parser.add_argument('--indent', type=int, default=None, + help='Indent output by n spaces') +parser.add_argument('-v', '--version', action='version', + version='%(prog)s ' + jsonpointer.__version__) + + +def main(): + try: + resolve_files() + except KeyboardInterrupt: + sys.exit(1) + + +def parse_pointer(args): + if args.POINTER: + ptr = args.POINTER + elif args.pointer_file: + ptr = args.pointer_file.read().strip() + else: + parser.print_usage() + sys.exit(1) + + return ptr + + +def resolve_files(): + """ Resolve a JSON pointer on JSON files """ + args = parser.parse_args() + + ptr = parse_pointer(args) + + for f in args.FILE: + doc = json.load(f) + try: + result = jsonpointer.resolve_pointer(doc, ptr) + print(json.dumps(result, indent=args.indent)) + except jsonpointer.JsonPointerException as e: + print('Could not resolve pointer: %s' % str(e), file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/robot/bin/jsonschema b/robot/bin/jsonschema new file mode 100755 index 0000000000000000000000000000000000000000..30fe0b5afe5eef08d320b4195a7796afc9cfc127 --- /dev/null +++ b/robot/bin/jsonschema @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from jsonschema.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/pbr b/robot/bin/pbr new file mode 100755 index 0000000000000000000000000000000000000000..b972910700ca2fa40c3cdbdc8a0a61160722d390 --- /dev/null +++ b/robot/bin/pbr @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from pbr.cmd.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/pip b/robot/bin/pip new file mode 100755 index 0000000000000000000000000000000000000000..17e9631127f4c8ca1afefb69cd1ff2c7f2656a1c --- /dev/null +++ b/robot/bin/pip @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/pip-3.8 b/robot/bin/pip-3.8 new file mode 100755 index 0000000000000000000000000000000000000000..17e9631127f4c8ca1afefb69cd1ff2c7f2656a1c --- /dev/null +++ b/robot/bin/pip-3.8 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/pip3 b/robot/bin/pip3 new file mode 100755 index 0000000000000000000000000000000000000000..17e9631127f4c8ca1afefb69cd1ff2c7f2656a1c --- /dev/null +++ b/robot/bin/pip3 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/pip3.8 b/robot/bin/pip3.8 new file mode 100755 index 0000000000000000000000000000000000000000..17e9631127f4c8ca1afefb69cd1ff2c7f2656a1c --- /dev/null +++ b/robot/bin/pip3.8 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/pygmentize b/robot/bin/pygmentize new file mode 100755 index 0000000000000000000000000000000000000000..948300d64e48e74f436b55a07a6cc1dabf943e35 --- /dev/null +++ b/robot/bin/pygmentize @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from pygments.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/python b/robot/bin/python new file mode 120000 index 0000000000000000000000000000000000000000..ae65fdaa12936b0d7525b090d198249fa7623e66 --- /dev/null +++ b/robot/bin/python @@ -0,0 +1 @@ +/usr/bin/python3 \ No newline at end of file diff --git a/robot/bin/python3 b/robot/bin/python3 new file mode 120000 index 0000000000000000000000000000000000000000..d8654aa0e2f2f3c1760e0fcbcbb52c1c5941fba7 --- /dev/null +++ b/robot/bin/python3 @@ -0,0 +1 @@ +python \ No newline at end of file diff --git a/robot/bin/python3.8 b/robot/bin/python3.8 new file mode 120000 index 0000000000000000000000000000000000000000..d8654aa0e2f2f3c1760e0fcbcbb52c1c5941fba7 --- /dev/null +++ b/robot/bin/python3.8 @@ -0,0 +1 @@ +python \ No newline at end of file diff --git a/robot/bin/rebot b/robot/bin/rebot new file mode 100755 index 0000000000000000000000000000000000000000..03833ab107617827c376539d47749ac76713dbfc --- /dev/null +++ b/robot/bin/rebot @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from robot.rebot import rebot_cli +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(rebot_cli()) diff --git a/robot/bin/robot b/robot/bin/robot new file mode 100755 index 0000000000000000000000000000000000000000..a6e452112161841c157bb20ef40b429292ef84ab --- /dev/null +++ b/robot/bin/robot @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from robot.run import run_cli +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(run_cli()) diff --git a/robot/bin/rst2html.py b/robot/bin/rst2html.py new file mode 100755 index 0000000000000000000000000000000000000000..51e5f3f2f70e03bf8e7261d99de83e414b5460b6 --- /dev/null +++ b/robot/bin/rst2html.py @@ -0,0 +1,23 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python + +# $Id: rst2html.py 4564 2006-05-21 20:44:42Z wiemann $ +# Author: David Goodger +# Copyright: This module has been placed in the public domain. + +""" +A minimal front end to the Docutils Publisher, producing HTML. +""" + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline, default_description + + +description = ('Generates (X)HTML documents from standalone reStructuredText ' + 'sources. ' + default_description) + +publish_cmdline(writer_name='html', description=description) diff --git a/robot/bin/rst2html4.py b/robot/bin/rst2html4.py new file mode 100755 index 0000000000000000000000000000000000000000..35447d7dc938c11c8a9803c86b7e84061df8980e --- /dev/null +++ b/robot/bin/rst2html4.py @@ -0,0 +1,26 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python + +# $Id: rst2html4.py 7994 2016-12-10 17:41:45Z milde $ +# Author: David Goodger +# Copyright: This module has been placed in the public domain. + +""" +A minimal front end to the Docutils Publisher, producing (X)HTML. + +The output conforms to XHTML 1.0 transitional +and almost to HTML 4.01 transitional (except for closing empty tags). +""" + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline, default_description + + +description = ('Generates (X)HTML documents from standalone reStructuredText ' + 'sources. ' + default_description) + +publish_cmdline(writer_name='html4', description=description) diff --git a/robot/bin/rst2html5.py b/robot/bin/rst2html5.py new file mode 100755 index 0000000000000000000000000000000000000000..43d5aae6398ba183665cc4656d89f8be4cffa386 --- /dev/null +++ b/robot/bin/rst2html5.py @@ -0,0 +1,35 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf8 -*- +# :Copyright: © 2015 Günter Milde. +# :License: Released under the terms of the `2-Clause BSD license`_, in short: +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. +# This file is offered as-is, without any warranty. +# +# .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause +# +# Revision: $Revision: 8410 $ +# Date: $Date: 2019-11-04 22:14:43 +0100 (Mo, 04. Nov 2019) $ + +""" +A minimal front end to the Docutils Publisher, producing HTML 5 documents. + +The output also conforms to XHTML 1.0 transitional +(except for the doctype declaration). +""" + +try: + import locale # module missing in Jython + locale.setlocale(locale.LC_ALL, '') +except locale.Error: + pass + +from docutils.core import publish_cmdline, default_description + +description = (u'Generates HTML 5 documents from standalone ' + u'reStructuredText sources ' + + default_description) + +publish_cmdline(writer_name='html5', description=description) diff --git a/robot/bin/rst2latex.py b/robot/bin/rst2latex.py new file mode 100755 index 0000000000000000000000000000000000000000..f6e98135236151fafaa6b57a703199e44993b957 --- /dev/null +++ b/robot/bin/rst2latex.py @@ -0,0 +1,26 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python + +# $Id: rst2latex.py 5905 2009-04-16 12:04:49Z milde $ +# Author: David Goodger +# Copyright: This module has been placed in the public domain. + +""" +A minimal front end to the Docutils Publisher, producing LaTeX. +""" + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline + +description = ('Generates LaTeX documents from standalone reStructuredText ' + 'sources. ' + 'Reads from (default is stdin) and writes to ' + ' (default is stdout). See ' + ' for ' + 'the full reference.') + +publish_cmdline(writer_name='latex', description=description) diff --git a/robot/bin/rst2man.py b/robot/bin/rst2man.py new file mode 100755 index 0000000000000000000000000000000000000000..ced92caa5dda0163424c849dd33041652d3f38de --- /dev/null +++ b/robot/bin/rst2man.py @@ -0,0 +1,26 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python + +# Author: +# Contact: grubert@users.sf.net +# Copyright: This module has been placed in the public domain. + +""" +man.py +====== + +This module provides a simple command line interface that uses the +man page writer to output from ReStructuredText source. +""" + +import locale +try: + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline, default_description +from docutils.writers import manpage + +description = ("Generates plain unix manual documents. " + default_description) + +publish_cmdline(writer=manpage.Writer(), description=description) diff --git a/robot/bin/rst2odt.py b/robot/bin/rst2odt.py new file mode 100755 index 0000000000000000000000000000000000000000..5a451ca007dd1b27b342ad309f9852a74c9f3b5c --- /dev/null +++ b/robot/bin/rst2odt.py @@ -0,0 +1,30 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python + +# $Id: rst2odt.py 5839 2009-01-07 19:09:28Z dkuhlman $ +# Author: Dave Kuhlman +# Copyright: This module has been placed in the public domain. + +""" +A front end to the Docutils Publisher, producing OpenOffice documents. +""" + +import sys +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline_to_binary, default_description +from docutils.writers.odf_odt import Writer, Reader + + +description = ('Generates OpenDocument/OpenOffice/ODF documents from ' + 'standalone reStructuredText sources. ' + default_description) + + +writer = Writer() +reader = Reader() +output = publish_cmdline_to_binary(reader=reader, writer=writer, + description=description) + diff --git a/robot/bin/rst2odt_prepstyles.py b/robot/bin/rst2odt_prepstyles.py new file mode 100755 index 0000000000000000000000000000000000000000..69c4a9ba5f0a010a69d4a1771a36cf7006168efd --- /dev/null +++ b/robot/bin/rst2odt_prepstyles.py @@ -0,0 +1,67 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python + +# $Id: rst2odt_prepstyles.py 8346 2019-08-26 12:11:32Z milde $ +# Author: Dave Kuhlman +# Copyright: This module has been placed in the public domain. + +""" +Fix a word-processor-generated styles.odt for odtwriter use: Drop page size +specifications from styles.xml in STYLE_FILE.odt. +""" + +# Author: Michael Schutte + +from __future__ import print_function + +from lxml import etree +import sys +import zipfile +from tempfile import mkstemp +import shutil +import os + +NAMESPACES = { + "style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0", + "fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" +} + + +def prepstyle(filename): + + zin = zipfile.ZipFile(filename) + styles = zin.read("styles.xml") + + root = etree.fromstring(styles) + for el in root.xpath("//style:page-layout-properties", + namespaces=NAMESPACES): + for attr in el.attrib: + if attr.startswith("{%s}" % NAMESPACES["fo"]): + del el.attrib[attr] + + tempname = mkstemp() + zout = zipfile.ZipFile(os.fdopen(tempname[0], "w"), "w", + zipfile.ZIP_DEFLATED) + + for item in zin.infolist(): + if item.filename == "styles.xml": + zout.writestr(item, etree.tostring(root)) + else: + zout.writestr(item, zin.read(item.filename)) + + zout.close() + zin.close() + shutil.move(tempname[1], filename) + + +def main(): + args = sys.argv[1:] + if len(args) != 1: + print(__doc__, file=sys.stderr) + print("Usage: %s STYLE_FILE.odt\n" % sys.argv[0], file=sys.stderr) + sys.exit(1) + filename = args[0] + prepstyle(filename) + + +if __name__ == '__main__': + main() diff --git a/robot/bin/rst2pseudoxml.py b/robot/bin/rst2pseudoxml.py new file mode 100755 index 0000000000000000000000000000000000000000..159d59afc4eea87e04382cfec5c600f35c0c321f --- /dev/null +++ b/robot/bin/rst2pseudoxml.py @@ -0,0 +1,23 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python + +# $Id: rst2pseudoxml.py 4564 2006-05-21 20:44:42Z wiemann $ +# Author: David Goodger +# Copyright: This module has been placed in the public domain. + +""" +A minimal front end to the Docutils Publisher, producing pseudo-XML. +""" + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline, default_description + + +description = ('Generates pseudo-XML from standalone reStructuredText ' + 'sources (for testing purposes). ' + default_description) + +publish_cmdline(description=description) diff --git a/robot/bin/rst2s5.py b/robot/bin/rst2s5.py new file mode 100755 index 0000000000000000000000000000000000000000..c80982dc7a5b3a122b9856514213b1d5bfb7ef44 --- /dev/null +++ b/robot/bin/rst2s5.py @@ -0,0 +1,24 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python + +# $Id: rst2s5.py 4564 2006-05-21 20:44:42Z wiemann $ +# Author: Chris Liechti +# Copyright: This module has been placed in the public domain. + +""" +A minimal front end to the Docutils Publisher, producing HTML slides using +the S5 template system. +""" + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline, default_description + + +description = ('Generates S5 (X)HTML slideshow documents from standalone ' + 'reStructuredText sources. ' + default_description) + +publish_cmdline(writer_name='s5', description=description) diff --git a/robot/bin/rst2xetex.py b/robot/bin/rst2xetex.py new file mode 100755 index 0000000000000000000000000000000000000000..d5cfac9ccacdc668b186f17404b0787f5f7907ac --- /dev/null +++ b/robot/bin/rst2xetex.py @@ -0,0 +1,27 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python + +# $Id: rst2xetex.py 7847 2015-03-17 17:30:47Z milde $ +# Author: Guenter Milde +# Copyright: This module has been placed in the public domain. + +""" +A minimal front end to the Docutils Publisher, producing Lua/XeLaTeX code. +""" + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline + +description = ('Generates LaTeX documents from standalone reStructuredText ' + 'sources for compilation with the Unicode-aware TeX variants ' + 'XeLaTeX or LuaLaTeX. ' + 'Reads from (default is stdin) and writes to ' + ' (default is stdout). See ' + ' for ' + 'the full reference.') + +publish_cmdline(writer_name='xetex', description=description) diff --git a/robot/bin/rst2xml.py b/robot/bin/rst2xml.py new file mode 100755 index 0000000000000000000000000000000000000000..d442b30a0219d33771b727e67fd51ae496707411 --- /dev/null +++ b/robot/bin/rst2xml.py @@ -0,0 +1,23 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python + +# $Id: rst2xml.py 4564 2006-05-21 20:44:42Z wiemann $ +# Author: David Goodger +# Copyright: This module has been placed in the public domain. + +""" +A minimal front end to the Docutils Publisher, producing Docutils XML. +""" + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline, default_description + + +description = ('Generates Docutils-native XML from standalone ' + 'reStructuredText sources. ' + default_description) + +publish_cmdline(writer_name='xml', description=description) diff --git a/robot/bin/rstpep2html.py b/robot/bin/rstpep2html.py new file mode 100755 index 0000000000000000000000000000000000000000..93d8f4ab831763865c5d6576c211c8695221126b --- /dev/null +++ b/robot/bin/rstpep2html.py @@ -0,0 +1,25 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python + +# $Id: rstpep2html.py 4564 2006-05-21 20:44:42Z wiemann $ +# Author: David Goodger +# Copyright: This module has been placed in the public domain. + +""" +A minimal front end to the Docutils Publisher, producing HTML from PEP +(Python Enhancement Proposal) documents. +""" + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline, default_description + + +description = ('Generates (X)HTML from reStructuredText-format PEP files. ' + + default_description) + +publish_cmdline(reader_name='pep', writer_name='pep_html', + description=description) diff --git a/robot/bin/swagger-flex b/robot/bin/swagger-flex new file mode 100755 index 0000000000000000000000000000000000000000..7fafc7bc860b939671646e7dfe76a203f9ceb383 --- /dev/null +++ b/robot/bin/swagger-flex @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from flex.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/tox b/robot/bin/tox new file mode 100755 index 0000000000000000000000000000000000000000..5e79f53a2c75760101002efb4a79602c326ce511 --- /dev/null +++ b/robot/bin/tox @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from tox.session import run_main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(run_main()) diff --git a/robot/bin/tox-quickstart b/robot/bin/tox-quickstart new file mode 100755 index 0000000000000000000000000000000000000000..3f0bfd2ab59e86cade2f457d21666fcecc6e5181 --- /dev/null +++ b/robot/bin/tox-quickstart @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from tox._quickstart import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/virtualenv b/robot/bin/virtualenv new file mode 100755 index 0000000000000000000000000000000000000000..5610eb51b840834c24e931b2e78981d0f8492c4d --- /dev/null +++ b/robot/bin/virtualenv @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from virtualenv.__main__ import run_with_catch +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(run_with_catch()) diff --git a/robot/bin/wheel b/robot/bin/wheel new file mode 100755 index 0000000000000000000000000000000000000000..6f23dc4adaeafb48a9bb0ede923768ae3e6fbc69 --- /dev/null +++ b/robot/bin/wheel @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from wheel.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/wheel-3.8 b/robot/bin/wheel-3.8 new file mode 100755 index 0000000000000000000000000000000000000000..6f23dc4adaeafb48a9bb0ede923768ae3e6fbc69 --- /dev/null +++ b/robot/bin/wheel-3.8 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from wheel.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/bin/wheel3 b/robot/bin/wheel3 new file mode 100755 index 0000000000000000000000000000000000000000..6f23dc4adaeafb48a9bb0ede923768ae3e6fbc69 --- /dev/null +++ b/robot/bin/wheel3 @@ -0,0 +1,8 @@ +#!/home/houcem/Desktop/egm/ngsi-ld-test-suite/robot/bin/python +# -*- coding: utf-8 -*- +import re +import sys +from wheel.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/AUTHORS.txt b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/AUTHORS.txt new file mode 100644 index 0000000000000000000000000000000000000000..72c87d7d38ae7bf859717c333a5ee8230f6ce624 --- /dev/null +++ b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/AUTHORS.txt @@ -0,0 +1,562 @@ +A_Rog +Aakanksha Agrawal <11389424+rasponic@users.noreply.github.com> +Abhinav Sagar <40603139+abhinavsagar@users.noreply.github.com> +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Tse +Adam Wentz +admin +Adrien Morison +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Aleks Bunin +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Alexey Popravka +Alli +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrey Bulgakov +Andrés Delfino <34587441+andresdelfino@users.noreply.github.com> +Andrés Delfino +Andy Freeland +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anuj Godase +AQNOUCH Mohammed +AraHaan +Arindam Choudhury +Armin Ronacher +Artem +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avner Cohen +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Darnell +Ben Hoyt +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Brandon L. Reiss +Brandt Bucher +Brett Randall +Brian Cristante <33549821+brcrista@users.noreply.github.com> +Brian Cristante +Brian Rosner +BrownTruck +Bruno Oliveira +Bruno Renié +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +c22 +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Chih-Hsuan Yen +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris McDonough +Chris Wolfe +Christian Heimes +Christian Oudard +Christopher Hunt +Christopher Snyder +Clark Boylan +Clay McClure +Cody +Cody Soyland +Colin Watson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Curtis Doty +cytolentino +Damian Quiroga +Dan Black +Dan Savilonis +Dan Sully +daniel +Daniel Collins +Daniel Hahler +Daniel Holth +Daniel Jost +Daniel Shaulov +Daniele Esposti +Daniele Procida +Danny Hermes +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Bordeynik +David Caro +David Evans +David Linke +David Pursehouse +David Tucker +David Wales +Davidovich +derwolfe +Desetude +Diego Caraballo +DiegoCaraballo +Dmitry Gladkov +Domen Kožar +Donald Stufft +Dongweiming +Douglas Thor +DrFeathers +Dustin Ingram +Dwayne Bailey +Ed Morley <501702+edmorley@users.noreply.github.com> +Ed Morley +Eitan Adler +ekristina +elainechan +Eli Schwartz +Eli Schwartz +Emil Burzo +Emil Styrke +Endoh Takanao +enoch +Erdinc Mutlu +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Ernest W Durbin III +Ernest W. Durbin III +Erwin Janssen +Eugene Vereshchagin +everdimension +Felix Yan +fiber-space +Filip Kokosiński +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gdanielson +Geoffrey Lehée +Geoffrey Sneddon +George Song +Georgi Valkov +Giftlin Rajaiah +gizmoguy1 +gkdoc <40815324+gkdoc@users.noreply.github.com> +Gopinath M <31352222+mgopi1990@users.noreply.github.com> +GOTO Hayato <3532528+gh640@users.noreply.github.com> +gpiks +Guilherme Espada +Guy Rozendorn +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +Herbert Pfennig +Hsiaoming Yang +Hugo +Hugo Lopes Tavares +Hugo van Kemenade +hugovk +Hynek Schlawack +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ilya Baryshev +INADA Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Ivan Pozdeev +Jacob Kim +jakirkham +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James Cleveland +James Cleveland +James Firth +James Polley +Jan Pokorný +Jannis Leidel +jarondl +Jason R. Coombs +Jay Graves +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jelmer Vernooij +jenix21 +Jeremy Stanley +Jeremy Zafran +Jiashuo Li +Jim Garrison +Jivan Amara +John Paton +John-Scott Atlakson +johnthagen +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joost Molenaar +Jorge Niedbalski +Joseph Long +Josh Bronson +Josh Hansen +Josh Schneier +Juanjo Bazán +Julian Berman +Julian Gethmann +Julien Demoor +jwg4 +Jyrki Pulliainen +Kai Chen +Kamal Bin Mustafa +kaustav haldar +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kexuan Sun +Kit Randel +kpinc +Krishna Oza +Kumar McMillan +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurie Opperman +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +Loren Carvalho +Lucas Cimon +Ludovic Gasc +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +László Kiss Kollár +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark Williams +Mark Williams +Markus Hametner +Masaki +Masklinn +Matej Stuchlik +Mathew Jennings +Mathieu Bridon +Matt Good +Matt Maker +Matt Robenolt +matthew +Matthew Einhorn +Matthew Gilliard +Matthew Iversen +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna <44498973+mbaluna@users.noreply.github.com> +mdebi <17590103+mdebi@users.noreply.github.com> +memoselyk +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Williamson +michaelpacer +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Monica Baluna +montefra +Monty Taylor +Nate Coraor +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nikhil Benesch +Nitesh Sharma +Nowell Strite +NtaleGrey +nvdv +Ofekmeister +ofrinevo +Oliver Jeeves +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +OMOTO Kenji +Omry Yadan +Oren Held +Oscar Benjamin +Oz N Tiram +Pachwenko <32424503+Pachwenko@users.noreply.github.com> +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavithra Eswaramoorthy <33131404+QueenCoffee@users.noreply.github.com> +Pawel Jasinski +Pekka Klärck +Peter Lisák +Peter Waller +petr-tik +Phaneendra Chiruvella +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Pratik Mallya +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal <7895pulkit@gmail.com> +Qiangning Hong +Quentin Pradet +R. David Murray +Rafael Caricio +Ralf Schmitt +Razzi Abuissa +rdb +Remi Rampin +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Richard Jones +RobberPhex +Robert Collins +Robert McGibbon +Robert T. McGibbon +robin elisha robinson +Roey Berman +Rohan Jain +Rohan Jain +Rohan Jain +Roman Bogorodskiy +Romuald Brunet +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +Roy Wellington Ⅳ +Ryan Wooden +ryneeverett +Sachi King +Salvatore Rinchiera +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sergey Vasilyev +Seth Woodworth +Shlomi Fish +Shovan Maity +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +Sorin Sbarnea +Stavros Korokithakis +Stefan Scherfke +Stephan Erb +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +stonebig +Stéphane Bidoul (ACSONE) +Stéphane Bidoul +Stéphane Klein +Sumana Harihareswara +Sviatoslav Sydorenko +Sviatoslav Sydorenko +Swat009 +Takayuki SHIMIZUKAWA +tbeswick +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tom Forbes +Tom Freudenheim +Tom V +Tomas Orsava +Tomer Chachamu +Tony Beswick +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +Travis Swicegood +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Philippon +Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> +Vitaly Babiy +Vladimir Rutsky +W. Trevor King +Wil Tan +Wilfred Hughes +William ML Leslie +William T Olson +Wilson Mo +wim glenn +Wolfgang Maier +Xavier Fernandez +Xavier Fernandez +xoviat +xtreak +YAMAMOTO Takashi +Yen Chi Hsuan +Yeray Diaz Diaz +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Zearin +Zearin +Zhiping Deng +Zvezdan Petkovic +Łukasz Langa +Семён Марьясин diff --git a/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..737fec5c5352af3d9a6a47a0670da4bdb52c5725 --- /dev/null +++ b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2019 The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/METADATA b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..3119fa91afb9c067f0d78f959ca038b7c0f86a2c --- /dev/null +++ b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/METADATA @@ -0,0 +1,71 @@ +Metadata-Version: 2.1 +Name: CacheControl +Version: 0.12.6 +Summary: httplib2 caching for requests +Home-page: https://github.com/ionrock/cachecontrol +Author: Eric Larson +Author-email: eric@ionrock.org +License: UNKNOWN +Keywords: requests http caching web +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +Provides-Extra: filecache +Requires-Dist: lockfile (>=0.9) ; extra == 'filecache' +Provides-Extra: redis +Requires-Dist: redis (>=2.10.5) ; extra == 'redis' + +============== + CacheControl +============== + +.. image:: https://img.shields.io/pypi/v/cachecontrol.svg + :target: https://pypi.python.org/pypi/cachecontrol + :alt: Latest Version + +.. image:: https://travis-ci.org/ionrock/cachecontrol.png?branch=master + :target: https://travis-ci.org/ionrock/cachecontrol + +CacheControl is a port of the caching algorithms in httplib2_ for use with +requests_ session object. + +It was written because httplib2's better support for caching is often +mitigated by its lack of thread safety. The same is true of requests in +terms of caching. + + +Quickstart +========== + +.. code-block:: python + + import requests + + from cachecontrol import CacheControl + + + sess = requests.session() + cached_sess = CacheControl(sess) + + response = cached_sess.get('http://google.com') + +If the URL contains any caching based headers, it will cache the +result in a simple dictionary. + +For more info, check out the docs_ + +.. _docs: http://cachecontrol.readthedocs.org/en/latest/ +.. _httplib2: https://github.com/jcgregorio/httplib2 +.. _requests: http://docs.python-requests.org/ + + diff --git a/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/RECORD b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..33d3e083f41ee3e6911d8266541bb2ba00b64666 --- /dev/null +++ b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/RECORD @@ -0,0 +1,37 @@ +cachecontrol/__init__.py,sha256=pJtAaUxOsMPnytI1A3juAJkXYDr8krdSnsg4Yg3OBEg,302 +cachecontrol/_cmd.py,sha256=88j4P3JlJGqg6xAXR4btN9fYruXUH4CE-M93Sie5IB8,1242 +cachecontrol/adapter.py,sha256=ctnbSXDOj0V0NaxJP2jFauOYRDHaNYMP9QCE8kB4kfk,4870 +cachecontrol/cache.py,sha256=1fc4wJP8HYt1ycnJXeEw5pCpeBL2Cqxx6g9Fb0AYDWQ,805 +cachecontrol/compat.py,sha256=Fn_aYzqNbN0bK9gUn8SQUzMLxQ_ruGnsEMvryYDFh3o,647 +cachecontrol/controller.py,sha256=fpLmIvxce2mKVFmtDFiiyydqU_pPbCucYLC9qP-LqvY,14137 +cachecontrol/filewrapper.py,sha256=vACKO8Llzu_ZWyjV1Fxn1MA4TGU60N5N3GSrAFdAY2Q,2533 +cachecontrol/heuristics.py,sha256=BFGHJ3yQcxvZizfo90LLZ04T_Z5XSCXvFotrp7Us0sc,4070 +cachecontrol/serialize.py,sha256=Jms7OS4GB2JFUzuMPlmQtuCDzcjjE-2ijrHpUXC2BV0,7062 +cachecontrol/wrapper.py,sha256=5LX0uJwkNQUtYSEw3aGmGu9WY8wGipd81mJ8lG0d0M4,690 +cachecontrol/caches/__init__.py,sha256=-gHNKYvaeD0kOk5M74eOrsSgIKUtC6i6GfbmugGweEo,86 +cachecontrol/caches/file_cache.py,sha256=nYVKsJtXh6gJXvdn1iWyrhxvkwpQrK-eKoMRzuiwkKk,4153 +cachecontrol/caches/redis_cache.py,sha256=yZP1PoUgAvxEZZrCVwImZ-5pFKU41v5HYJf1rfbXYmM,844 +CacheControl-0.12.6.dist-info/AUTHORS.txt,sha256=RtqU9KfonVGhI48DAA4-yTOBUhBtQTjFhaDzHoyh7uU,21518 +CacheControl-0.12.6.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090 +CacheControl-0.12.6.dist-info/METADATA,sha256=DzDqga-Bw7B7ylVrJanU_-imHV1od8Ok64qr5ugT-5A,2107 +CacheControl-0.12.6.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +CacheControl-0.12.6.dist-info/top_level.txt,sha256=vGYWzpbe3h6gkakV4f7iCK2x3KyK3oMkV5pe5v25-d4,13 +CacheControl-0.12.6.dist-info/RECORD,, +CacheControl-0.12.6.dist-info/INSTALLER,, +CacheControl-0.12.6.dist-info/__pycache__,, +cachecontrol/_cmd.cpython-38.pyc,, +cachecontrol/heuristics.cpython-38.pyc,, +CacheControl-0.12.6.virtualenv,, +cachecontrol/controller.cpython-38.pyc,, +cachecontrol/caches/file_cache.cpython-38.pyc,, +cachecontrol/adapter.cpython-38.pyc,, +cachecontrol/__pycache__,, +cachecontrol/serialize.cpython-38.pyc,, +cachecontrol/__init__.cpython-38.pyc,, +cachecontrol/filewrapper.cpython-38.pyc,, +cachecontrol/caches/__init__.cpython-38.pyc,, +cachecontrol/caches/redis_cache.cpython-38.pyc,, +cachecontrol/wrapper.cpython-38.pyc,, +cachecontrol/compat.cpython-38.pyc,, +cachecontrol/caches/__pycache__,, +cachecontrol/cache.cpython-38.pyc,, \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/WHEEL b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ef99c6cf3283b50a273ac4c6d009a0aa85597070 --- /dev/null +++ b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..af37ac627514445bfc761e8867d8c178ea766e4b --- /dev/null +++ b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.dist-info/top_level.txt @@ -0,0 +1 @@ +cachecontrol diff --git a/robot/lib/python3.8/site-packages/CacheControl-0.12.6.virtualenv b/robot/lib/python3.8/site-packages/CacheControl-0.12.6.virtualenv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/HTTPDLibrary/__init__.py b/robot/lib/python3.8/site-packages/HTTPDLibrary/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5d9ea0144f773c2db6657f13845ef1909cde4138 --- /dev/null +++ b/robot/lib/python3.8/site-packages/HTTPDLibrary/__init__.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +from threading import Thread +import time +import datetime + +__author__ = 'mbbn' + +import pkg_resources + +from BaseHTTPServer import HTTPServer +from SimpleHTTPServer import SimpleHTTPRequestHandler +from SocketServer import ThreadingMixIn +from robot.api import logger + + +class HTTPDLibrary(object): + """ + + """ + ROBOT_LIBRARY_SCOPE = 'LOCAL' + __version__ = pkg_resources.get_distribution("robotframework-httpd").version + + def __init__(self, port, hostname='0.0.0.0'): + self.port = port + self.hostname = hostname + + def run_server(self, httpd_server, port): + try: + logger.info("Start Http Server with port {}.".format(port), also_console=True) + # httpd_server.server_name = self. + httpd_server.serve_forever() + except KeyboardInterrupt: + pass + + logger.info("Http Server with port {} is stop.".format(port), also_console=True) + httpd_server.shutdown() + + + def create_httpd(self, end_time): + try: + self.httpd = ThreadedHTTPD((self.hostname, int(self.port)), RequestHandler) + except Exception: + + # logger.info(traceback.format_exc(), also_console=True) + if datetime.datetime.now() < end_time: + time.sleep(2) + self.create_httpd(end_time) + + + + self.httpd.request_count = 0 + self.httpd.fails = [] + + def start_httpd(self, timeout=10): + end_time = datetime.datetime.now() + datetime.timedelta(seconds=int(timeout)) + try: + self.httpd + except (NameError, AttributeError) as e: + self.create_httpd(end_time) + t = Thread(name=self.port, target=self.run_server, args=(self.httpd, self.port)) + t.daemon = True + t.start() + time.sleep(1) + + def stop_httpd(self): + try: + self.httpd.shutdown() + except AttributeError: + pass + + def set_wished_request(self, wished_request): + self.wished_request = wished_request + try: + self.httpd.buffer = [] + except AttributeError: + end_time = datetime.datetime.now() + datetime.timedelta(seconds=int(10)) + self.create_httpd(end_time) + + self.httpd.buffer = [] + + + def wait_to_receive_request(self, wished_request=None, timeout=10): + if (wished_request == None and self.wished_request != None): + wished_request = self.wished_request + start = datetime.datetime.now() + while datetime.datetime.now() < start + datetime.timedelta(seconds=int(timeout)): + if len(self.httpd.buffer) > 0: + for buff in self.httpd.buffer: + if wished_request["method"] == buff["method"] and wished_request["path"] == buff["path"]: + if "body" in wished_request: + if wished_request["body"] == buff["body"]: + return + else: + return + time.sleep(1) + + # if datetime.datetime.now() > start + datetime.timedelta(seconds=int(timeout)): + self.stop_httpd() + raise Exception("Not Received Request after {} sec.".format(timeout)) + + + def wait_to_not_receive_request(self, timeout=10): + start = datetime.datetime.now() + while datetime.datetime.now() < start + datetime.timedelta(seconds=int(timeout)): + if len(self.httpd.buffer) > 0: + t = datetime.datetime.now() - start + exceptionError = "Server with port {} received {} request after {}" \ + "".format(self.port, len(self.httpd.buffer), t) + self.stop_httpd() + raise Exception(exceptionError) + + + def reset_buffer(self): + self.httpd.buffer = [] + + + def wait_to_recieve_count(self, count, timeout=10): + start = datetime.datetime.now() + count = int(count) + while datetime.datetime.now() < start + datetime.timedelta(seconds=int(timeout)): + if len(self.httpd.buffer) >= count: + break + + while datetime.datetime.now() < start + datetime.timedelta(seconds=int(timeout)): + if len(self.httpd.buffer) > count: + break + + if len(self.httpd.buffer) != count: + t = datetime.datetime.now() - start + exception_error = "Server with port {} received {} request after {}" \ + "".format(self.port, len(self.httpd.buffer), t) + self.stop_httpd() + raise Exception(exception_error) + + + def delay_run_httpd(self, delay_time, flag): + time.sleep(float(delay_time)) + try: + self.httpd + except (NameError, AttributeError) as e: + end_time = datetime.timedelta(seconds=int(10)) + self.create_httpd(end_time) + if flag == 'True': + self.httpd.buffer = self.buffer2 + self.run_server(self.httpd, self.port) + + + def delay_to_start_httpd(self, delay_time, flag=False): + t = Thread(name=self.port, target=self.delay_run_httpd, args=(delay_time, flag)) + t.daemon = True + t.start() + + + def delay_stop_httpd(self, delay_time, flag): + time.sleep(float(delay_time)) + if flag == 'True': + self.buffer2 = self.httpd.buffer + self.httpd.shutdown() + del self.httpd + + + def delay_to_stop_httpd(self, delay_time, flag=False): + t = Thread(name=self.port, target=self.delay_stop_httpd, args=(delay_time, flag)) + t.daemon = True + t.start() + + +class ThreadedHTTPD(ThreadingMixIn, HTTPServer): + buffer = [] + + def verify_request(self, request, client_address): + return HTTPServer.verify_request(self, request, client_address) + + + def shutdown(self): + HTTPServer.shutdown(self) + + def server_close(self): + HTTPServer.server_close(self) + + def add_to_buffer(self, method, path, body): + self.buffer += [{ + "method": method, + "path": path, + "body": body + }] + + def shutdown_request(self, request): + HTTPServer.shutdown_request(self, request) + + def close_request(self, request): + HTTPServer.close_request(self, request) + + +class RequestHandler(SimpleHTTPRequestHandler): + def __init__(self, request, client_address, server): + SimpleHTTPRequestHandler.__init__(self, request, client_address, server) + + def do_GET(self): + """Respond to a GET request.""" + content_len = int(self.headers.getheader('content-length', 0)) + body = self.rfile.read(content_len) + self.server.add_to_buffer("GET", self.path, body) + self.send_response(200) + + def do_POST(self): + """Respond to a POST request.""" + content_len = int(self.headers.getheader('content-length', 0)) + body = self.rfile.read(content_len) + self.server.add_to_buffer("POST", self.path, body) + self.send_response(200) + + def log_message(self, format, *args): + pass diff --git a/robot/lib/python3.8/site-packages/HttpCtrl/__init__.py b/robot/lib/python3.8/site-packages/HttpCtrl/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d4ffda456c295251b20271bbb4dfbe6479f52098 --- /dev/null +++ b/robot/lib/python3.8/site-packages/HttpCtrl/__init__.py @@ -0,0 +1,1172 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2019 +Copyright: GNU Public License + +HttpCtrl is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +HttpCtrl is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +import datetime +import http.client +import json +import threading + +from robot.api import logger + +from HttpCtrl.internal_messages import IgnoreRequest +from HttpCtrl.http_server import HttpServer +from HttpCtrl.request_storage import RequestStorage +from HttpCtrl.response_storage import ResponseStorage +from HttpCtrl.response import Response + + +class Client: + """ + + HTTP/HTTPS Client Library that provides comprehensive interface to Robot Framework to control HTTP/HTTPS client. + + See other HttpCtrl libraries: + + - HttpCtrl.Server_ - HTTP Server API for testing where easy-controlled HTTP server is required. + + - HttpCtrl.Json_ - Json related API for testing where work with Json message is required. + + .. _HttpCtrl.Server: server.html + .. _HttpCtrl.Json: json.html + + Example how to send GET request to obtain origin IP address and check that response is not empty: + + .. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Get Origin Address + Initialize Client www.httpbin.org + Send HTTP Request GET /ip + + ${response status}= Get Response Status + ${response body}= Get Response Body + + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${origin}= Get Json Value From String ${response body} origin + Should Not Be Empty ${origin} + + Example how to sent PATCH request using HTTPS: + + .. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Send HTTPS PATCH Request + Initialize Client www.httpbin.org + + ${body}= Set Variable { "volume": 77, "mute": false } + Send HTTPS Request PATCH /patch ${body} + + ${response status}= Get Response Status + ${response body}= Get Response Body + + ${expected status}= Convert To Integer 200 + Should Be Equal ${response status} ${expected status} + + ${volume}= Get Json Value From String ${response body} json/volume + Should Be Equal ${volume} ${77} + + ${mute}= Get Json Value From String ${response body} json/mute + Should Be Equal ${mute} ${False} + + """ + + def __init__(self): + self.__host = None + self.__port = None + + self.__request_headers = {} + + self.__response_guard = threading.Lock() + self.__response_status = None + self.__response_body = None + self.__response_headers = None + + self.__event_queue = threading.Condition() + self.__async_queue = {} + + + def initialize_client(self, host, port=None): + """ + + Initialize client using host and port of a server which will be used for communication. + + `host` [in] (string): Host of a server which client will use for communication. + + `port` [in] (string|integer): Port of a server which client will use for communication. Optional argument. + + Example when server is located on machine with address 192.168.0.1 and port 8000: + + +-------------------+-------------+------+ + | Initialize Client | 192.168.0.1 | 8000 | + +-------------------+-------------+------+ + + .. code:: text + + Initialize Client 192.168.0.1 8000 + + Example when your server has name: + + +-------------------+-----------------+ + | Initialize Client | www.httpbin.org | + +-------------------+-----------------+ + + .. code:: text + + Initialize Client www.httpbin.org + + """ + self.__host = host + self.__port = port or "" + + + def __send(self, connection_type, method, url, body): + if self.__host is None or self.__port is None: + raise AssertionError("Client is not initialized (host and port are empty).") + + endpoint = "%s:%s" % (self.__host, str(self.__port)) + + if connection_type == 'http': + connection = http.client.HTTPConnection(endpoint) + elif connection_type == 'https': + connection = http.client.HTTPSConnection(endpoint) + else: + raise AssertionError("Internal error of the client, please report to " + "'https://github.com/annoviko/robotframework-httpctrl/issues'.") + + connection.request(method, url, body, self.__request_headers) + + self.__request_headers = {} + + logger.info("Request (type: '%s', method '%s') is sent to %s." % (connection_type, method, endpoint)) + logger.info("%s %s" % (method, url)) + if body is not None: + logger.info("%s" % body) + + return connection + + + def __wait_response(self, connection): + try: + server_response = connection.getresponse() + + with self.__response_guard: + self.__response_status = server_response.status + self.__response_headers = server_response.headers + self.__response_body = server_response.read().decode('utf-8') + + except Exception as exception: + logger.info("Server has not provided response to the request (reason: %s)." % str(exception)) + + finally: + connection.close() + + + def __wait_response_async(self, connection): + try: + server_response = connection.getresponse() + + with self.__event_queue: + response = Response(server_response.status, server_response.read().decode('utf-8'), + server_response.headers) + + self.__async_queue[connection] = response + self.__event_queue.notify_all() + + except Exception as exception: + logger.info("Server has not provided response to the request (reason: %s)." % str(exception)) + + finally: + connection.close() + + + def __send_request(self, connection_type, method, url, body): + connection = self.__send(connection_type, method, url, body) + self.__wait_response(connection) + + + def __sent_request_async(self, connection_type, method, url, body): + connection = self.__send(connection_type, method, url, body) + + wait_thread = threading.Thread(target=self.__wait_response_async, args=(connection,)) + wait_thread.daemon = True + wait_thread.start() + + return connection + + + def send_http_request(self, method, url, body=None): + """ + + Send HTTP request with specified parameters. This function is blocked until server replies or + timeout connection. + + `method` [in] (string): Method that is used to send request (GET, POST, PUT, DELETE, etc., see: RFC 7231, RFC 5789). + + `url` [in] (string): Path to the resource, for example, in case address www.httpbin.org/ip - '/ip' is an path. + + `body` [in] (string): Body of the request. + + Example where GET request is sent to server: + + +-------------------+-----+-----+ + | Send HTTP Request | GET | /ip | + +-------------------+-----+-----+ + + .. code:: text + + Send HTTP Request GET /ip + + Example where POST request is sent with specific body: + + +-------------------+------+-------+-------------------------------+ + | Send HTTP Request | POST | /post | { "message": "Hello World!" } | + +-------------------+------+-------+-------------------------------+ + + .. code:: text + + ${body}= Set Variable { "message": "Hello World!" } + Send HTTP Request POST /post ${body} + + """ + self.__send_request('http', method, url, body) + + + def send_http_request_async(self, method, url, body=None): + """ + + Send HTTP request with specified parameters asynchronously. Non-blocking function to send request that waits + for reply using separate thread. Return connection object that is used as a key to get asynchronous response + using function 'Get Async Response'. + + `method` [in] (string): Method that is used to send request (GET, POST, PUT, DELETE, etc., see: RFC 7231, RFC 5789). + + `url` [in] (string): Path to the resource, for example, in case address www.httpbin.org/ip - '/ip' is an path. + + `body` [in] (string): Body of the request. + + Example where PUT request is sent with specific body: + + +----------------+-------------------------+-----+------+---------------+ + | ${connection}= | Send HTTP Request Async | PUT | /put | Hello Server! | + +----------------+-------------------------+-----+------+---------------+ + + .. code:: text + + ${connection}= Send HTTP Request Async PUT /put Hello Server! + + """ + return self.__sent_request_async('http', method, url, body) + + + def send_https_request(self, method, url, body=None): + """ + + Send HTTPS request with specified parameters. + + `method` [in] (string): Method that is used to send request (GET, POST, DELETE, etc., see: RFC 7231, RFC 5789). + + `url` [in] (string): Path to the resource, for example, in case address www.httpbin.org/ip - '/ip' is an path. + + `body` [in] (string): Body of the request. + + Example where PATCH request to update parameters: + + +--------------------+-------+--------+---------------------------------+ + | Send HTTPS Request | PATCH | /patch | { "volume": 77, "mute": false } | + +--------------------+-------+--------+---------------------------------+ + + .. code:: text + + ${body}= Set Variable { "volume": 77, "mute": false } + Send HTTPS Request PATCH /patch ${body} + + """ + self.__send_request('https', method, url, body) + + + def send_https_request_async(self, method, url, body=None): + """ + + Send HTTPS request with specified parameters asynchronously. Non-blocking function to send request that waits + for reply using separate thread. Return connection object that is used as a key to get asynchronous response + using function 'Get Async Response'. + + `method` [in] (string): Method that is used to send request (GET, POST, DELETE, etc., see: RFC 7231, RFC 5789). + + `url` [in] (string): Path to the resource, for example, in case address www.httpbin.org/ip - '/ip' is an path. + + `body` [in] (string): Body of the request. + + Example where DELETE request is sent with specific body: + + +----------------+--------------------------+--------+---------+ + | ${connection}= | Send HTTPS Request Async | DELETE | /delete | + +----------------+--------------------------+--------+---------+ + + .. code:: text + + ${connection}= Send HTTPS Request Async DELETE /delete + + """ + return self.__sent_request_async('http', method, url, body) + + + def set_request_header(self, key, value): + """ + + Set HTTP header for request that is going to be sent. Should be called before 'Send HTTP Request' or + 'Send HTTPS Request'. + + `key` [in] (string): Header name that should be used in the request (be aware of case-sensitive headers). + + `value` [in] (string): Value that corresponds to specified header. + + Example where several specific headers 'Content-Type' and 'Some Header' are set to request: + + +--------------------+------------------+-------------------+ + | Set Request Header | Important-Header | important-value | + +--------------------+------------------+-------------------+ + | Set Request Header | Some-Header | some-value-for-it | + +--------------------+------------------+-------------------+ + + .. code:: text + + Set Request Header Important-Header important-value + Set Request Header Some-Header some-value-for-it + + """ + self.__request_headers[key] = value + + + def get_response_status(self): + """ + + Return response code as an integer value. This method should be called once after 'Send HTTP Request' or + 'Send HTTPS Request'. It returns None, in case of attempt to get response code more then once or if + 'Send HTTP Request' or 'Send HTTPS Request' is not called before. + + Example how to get response code: + + +---------------------+---------------------+ + | ${response status}= | Get Response Status | + +---------------------+---------------------+ + + .. code:: text + + ${response status}= Get Response Status + + """ + with self.__response_guard: + status = self.__response_status + self.__response_status = None + return status + + + def get_response_headers(self): + """ + + Return response headers as a dictionary. This method should be called once after 'Send HTTP Request' or + 'Send HTTPS Request'. It returns None, in case of attempt to get response code more then once or if + 'Send HTTP Request' or 'Send HTTPS Request' is not called before. + + Example how to get response code: + + +----------------------+----------------------+ + | ${response headers}= | Get Response Headers | + +----------------------+----------------------+ + + .. code:: text + + ${response headers}= Get Response Headers + + """ + with self.__response_guard: + headers = self.__response_headers + self.__response_headers = None + return headers + + + def get_response_body(self): + """ + + Return response body as a string. This method should be called once after 'Send HTTP Request' or + 'Send HTTPS Request'. It returns None, in case of attempt to get response code more then once or if + 'Send HTTP Request' or 'Send HTTPS Request' is not called before. + + Example how to get response code: + + +-------------------+-------------------+ + | ${response body}= | Get Response Body | + +-------------------+-------------------+ + + .. code:: text + + ${response body}= Get Response Body + + """ + with self.__response_guard: + body = self.__response_body + self.__response_body = None + return body + + + def get_async_response(self, connection, timeout=0): + """ + + Return response as an object for the specified connection. This method should be called once after + 'Send HTTP Request Async' or 'Send HTTPS Request Async'. It returns None if there is no response for the + specified connection. + + `connection` [in] (object): Connection for that response should be obtained. + + `timeout` [in] (int): Period of time in seconds to obtain response (by default is 0). + + Example how to get response object: + + +--------------+--------------------+ + | ${response}= | Get Async Response | + +--------------+--------------------+ + + Example how to try to get response object during 10 seconds: + + +--------------+--------------------+----+ + | ${response}= | Get Async Response | 10 | + +--------------+--------------------+----+ + + .. code:: text + + ${connection}= Send HTTP Async Request POST /post Hello Server! + ${response}= Get Async Response ${connection} 5 + + """ + with self.__event_queue: + start_time = datetime.datetime.now() + end_time = start_time + + while (end_time - start_time).total_seconds() < int(timeout): + if connection in self.__async_queue: + return self.__async_queue.pop(connection) + + def predicate(): + return connection in self.__async_queue + + self.__event_queue.wait_for(predicate, int(timeout)) + end_time = datetime.datetime.now() + + return self.__async_queue.pop(connection, None) + + + def get_status_from_response(self, response): + """ + + Return response status as an integer value from the specified response object that was obtained by function + 'Get Async Response'. Return 'None' if response object is None. + + Example how to get response status from a response object: + + +---------------------+--------------------------+-------------+ + | ${response status}= | Get Status From Response | ${response} | + +---------------------+--------------------------+-------------+ + + .. code:: text + + ${connection}= Send HTTP Async Request GET /get + + # Some other actions ... + + ${response}= Get Async Response ${connection} 5 + ${response status}= Get Status From Response ${response} + + """ + if response is None: + logger.error("Impossible to get status from 'None' response object.") + return None + + return response.get_status() + + + def get_headers_from_response(self, response): + """ + + Return response headers as a map from the specified response object that was obtained by function + 'Get Async Response'. Return 'None' if response object is None. + + Example how to get response headers from a response object: + + +----------------------+---------------------------+-------------+ + | ${response headers}= | Get Headers From Response | ${response} | + +----------------------+---------------------------+-------------+ + + .. code:: text + + ${connection}= Send HTTP Async Request GET /get + + # Some other actions ... + + ${response}= Get Async Response ${connection} 5 + ${response headers}= Get Headers From Response ${response} + + """ + if response is None: + logger.error("Impossible to get headers from 'None' response object.") + return None + + return response.get_headers() + + + def get_body_from_response(self, response): + """ + + Return response body as a string from the specified response object that was obtained by function + 'Get Async Response'. Return 'None' if response object is None. + + Example how to get response code from a response object: + + +-------------------+------------------------+-------------+ + | ${response body}= | Get Body From Response | ${response} | + +-------------------+------------------------+-------------+ + + .. code:: text + + ${connection}= Send HTTP Async Request GET /get + + # Some other actions ... + + ${response}= Get Async Response ${connection} 5 + ${response body}= Get Body From Response ${response} + + """ + if response is None: + logger.error("Impossible to get body from 'None' response object.") + return None + + return response.get_body() + + + +class Server: + """ + + HTTP Server Library that provides comprehensive interface to Robot Framework to control HTTP server. + + See other HttpCtrl libraries: + + - HttpCtrl.Client_ - HTTP/HTTP Client API for testing where easy-controlled HTTP/HTTPS client is required. + + - HttpCtrl.Json_ - Json related API for testing where work with Json message is required. + + .. _HttpCtrl.Client: client.html + .. _HttpCtrl.Json: json.html + + Here is an example of receiving POST request. In this example HTTP client sends POST request to HTTP server. HTTP + server receives it and checks incoming request for correctness. + + .. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Client + Library HttpCtrl.Server + + Test Setup Initialize HTTP Client And Server + Test Teardown Terminate HTTP Server + + *** Test Cases *** + + Receive And Reply To POST + ${request body}= Set Variable { "message": "Hello!" } + Send HTTP Request Async POST /post ${request body} + + Wait For Request + Reply By 200 + + ${method}= Get Request Method + ${url}= Get Request Url + ${body}= Get Request Body + + Should Be Equal ${method} POST + Should Be Equal ${url} /post + Should Be Equal ${body} ${request body} + + *** Keywords *** + + Initialize HTTP Client And Server + Initialize Client 127.0.0.1 8000 + Start Server 127.0.0.1 8000 + + Terminate HTTP Server + Stop Server + + """ + + def __init__(self): + self.__response_headers = {} + self.__request = None + + self.__server = None + self.__thread = None + + + def start_server(self, host, port): + """ + + Start HTTP server on specific address and port. Server should be closed when it is not required, for example, + when test is over. In case of double call of \`Start Server\`, the previous will be stopped and only then the + next one HTTP server will be started. + + `host` [in] (string): Address that will be used by HTTP server to listen. + + `port` [in] (string): Port that will be used by HTTP server to listen. + + Example how to initialize server: + + +--------------+-----------+------+ + | Start Server | 127.0.0.1 | 8000 | + +--------------+-----------+------+ + + .. code:: text + + Start Server 127.0.0.1 8000 + + It is a good practice to start server and stop it using 'Test Setup' and 'Test Teardown', for example: + + .. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Server + + Test Setup Initialize HTTP Server + Test Teardown Terminate HTTP Server + + *** Test Cases *** + + HTTP Server Based Test + Wait For Request + Reply By 200 + + # Check incoming request + + *** Keywords *** + + Initialize HTTP Server + Start Server 127.0.0.1 8000 + + Terminate HTTP Server + Stop Server + + """ + + self.stop_server() + + logger.info("Prepare HTTP server '%s:%s' and thread to serve it." % (host, port)) + + self.__server = HttpServer(host, int(port)) + self.__thread = threading.Thread(target=self.__server.start, args=()) + self.__thread.start() + + # In case of start-stop, stop may be finished before server start and ir will be impossible to join thread. + self.__server.wait_run_state() + + + def stop_server(self): + """ + + Stop HTTP server if it has been started. This function should be called if server has been started. + + Example how to stop server: + + +-------------+ + | Stop Server | + +-------------+ + + .. code:: text + + Stop Server + + It is a good practice to start server and stop it using `Test Setup` and `Test Teardown` - see example for + \`Start Server\`. + + """ + if self.__server is not None: + self.__server.stop() + self.__thread.join() + + self.__response_headers = {} + self.__request = None + + ResponseStorage().clear() + RequestStorage().clear() + + self.__server = None + self.__thread = None + + logger.info("HTTP server is stopped.") + + + def wait_for_request(self, timeout=5): + """ + + Command to server to wait incoming request. This call is blocked until HTTP request arrives. Basically server + receives all requests after \`Start Server\` and places them to internal queue. When test call function + \`Wait For Request\` it checks the queue and if it is not empty returns the first request in the queue. If the + queue is empty then function waits when the server receives request and place it to the queue. There is + default time period '5 seconds' to wait request and this waiting time can be changed. If during wait time the + request is not received then timeout error occurs. + + `timeout` [in] (int): Period of time in seconds when a request should be received by HTTP server. + + Example how to wait request. + + +------------------+ + | Wait For Request | + +------------------+ + + .. code:: text + + Wait For Request + + Example how to wait request during 2 seconds. + + +------------------+---+ + | Wait For Request | 2 | + +------------------+---+ + + .. code:: text + + Wait For Request 2 + + """ + self.__request = RequestStorage().pop(int(timeout)) + if self.__request is None: + raise AssertionError("Timeout: request was not received.") + + logger.info("Request is received: %s" % self.__request) + + + def wait_for_no_request(self, timeout=5.0): + """ + + Command to server to wait for no incoming request during specific time. This call is blocked until HTTP request + arrives or timeout. Basically server receives all requests after \`Start Server\` and places them to internal + queue. When test call function \`Wait For No Request\` it checks the queue and if it is not empty returns throws + exception. Otherwise it waits for request during 'timeout' seconds. If during this time request is received then + exception is thrown. + + `timeout` [in] (int): Period of time in seconds when requests should not be received by HTTP server. + + Example how to wait for lack of requests. + + +---------------------+ + | Wait For No Request | + +---------------------+ + + .. code:: text + + Wait For No Request + + Example how to wait for lack of requests during 10 seconds. + + +---------------------+----+ + | Wait For No Request | 10 | + +---------------------+----+ + + .. code:: text + + Wait For No Request 10 + + """ + self.__request = RequestStorage().pop(int(timeout)) + if self.__request is not None: + raise AssertionError("Request was received: %s." % self.__request) + + logger.info("Request is not received.") + + + def wait_and_ignore_request(self): + """ + + Command to server to wait incoming request and ignore it by closing connection. This call is blocked until HTTP + request arrives. Basically server receives all requests after \`Start Server\` and places them to internal + queue. When test call function \`Wait And Ignore Request\` it checks the queue and if it is not empty returns + the first request in the queue is ignore and connection is closed. If the queue is empty then function waits + when the server receives request and place it to the queue. + + Example how to wait and ignore request. + + +-------------------------+ + | Wait And Ignore Request | + +-------------------------+ + + .. code:: text + + Wait And Ignore Request + + """ + self.wait_for_request() + ResponseStorage().push(IgnoreRequest()) + logger.info("Request is ignored by closing connection.") + + + def get_request_method(self): + """ + + Returns method of received request as a string. This function should be called after \`Wait For Request\`, + otherwise None is returned. + + Example how to obtain method of incoming request: + + +--------------------+ + | Get Request Method | + +--------------------+ + + .. code:: text + + Get Request Method + + """ + return self.__request.get_method() + + + def get_request_body(self): + """ + + Returns body of received request as a string. This function should be called after \`Wait For Request\`, + otherwise None is returned. + + Example how to obtain body of incoming request: + + +------------------+ + | Get Request Body | + +------------------+ + + .. code:: text + + Get Request Body + + """ + return self.__request.get_body() + + + def get_request_headers(self): + """ + + Returns headers of received request as a dictionary. This function should be called after \`Wait For Request\`, + otherwise None is returned. + + Example how to obtain headers of incoming request: + + +---------------------+ + | Get Request Headers | + +---------------------+ + + .. code:: text + + Get Request Headers + + """ + return self.__request.get_headers() + + + def get_request_url(self): + """ + + Returns URL of received request as a string. This function should be called after \`Wait For Request\`, + otherwise None is returned. + + Example how to obtain URL of incoming request: + + +-----------------+ + | Get Request Url | + +-----------------+ + + .. code:: text + + Get Request Url + + """ + return self.__request.get_url() + + + def set_reply_header(self, key, value): + """ + + Set or insert new (if it does not exist yet) header to HTTP response. To send response itself function + \`Reply By\` is used. + + `key` [in] (string): HTTP header name. + `value` [in] (string): HTTP header value. + + Example how to set header for HTTP response: + + +------------------+--------+----------------+ + | Set Reply Header | Origin | 127.0.0.1:8000 | + +------------------+--------+----------------+ + + .. code:: text + + Set Reply Header Origin 127.0.0.1:8000 + + Example how to set several headers for HTTP response: + + +------------------+-------------+----------------+ + | Set Reply Header | Origin | 127.0.0.1:8001 | + +------------------+-------------+----------------+ + | Set Reply Header | City-Source | St.-Petersburg | + +------------------+-------------+----------------+ + + .. code:: text + + Set Reply Header Origin 127.0.0.1:8000 + Set Reply Header City-Source St.-Petersburg + + """ + self.__response_headers[key] = value + + + def reply_by(self, status, body=None): + """ + + Send response using specified HTTP code and body. This function should be called after \`Wait For Request\`. + + `status` [in] (string): HTTP status code for response. + `body` [in] (string): Body that should contain response. + + Example how to reply by 204 (No Content) to incoming request: + + +----------+-----+ + | Reply By | 204 | + +----------+-----+ + + .. code:: text + + Reply By 204 + + Example how to reply 200 (OK) with body to incoming request: + + +----------+-----+--------------------------+ + | Reply By | 200 | { "status": "accepted" } | + +----------+-----+--------------------------+ + + .. code:: text + + ${response body}= Set Variable { "status": "accepted" } + Reply By 204 ${response body} + + """ + response = Response(int(status), body, self.__response_headers) + ResponseStorage().push(response) + + + +class Json: + """ + + Json Library provide comprehensive interface to Robot Framework to work with JSON structures that are actively + used for Internet communication nowadays. + + See other HttpCtrl libraries: + + - HttpCtrl.Client_ - HTTP/HTTP Client API for testing where easy-controlled HTTP/HTTPS client is required. + + - HttpCtrl.Server_ - HTTP Server API for testing where easy-controlled HTTP server is required. + + .. _HttpCtrl.Client: client.html + .. _HttpCtrl.Server: server.html + + Example where Json values are updated in a string and then read from it: + + .. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Write And Read Json Nesting Value + ${json template}= Catenate + ... { + ... "book": { + ... "title": "St Petersburg: A Cultural History", + ... "author": "Solomon Volkov", + ... "price": 0, + ... "currency": "" + ... } + ... } + + ${catalog}= Set Json Value In String ${json template} book/price ${500} + ${catalog}= Set Json Value In String ${catalog} book/currency RUB + + ${title}= Get Json Value From String ${catalog} book/title + ${price}= Get Json Value From String ${catalog} book/price + ${currency}= Get Json Value From String ${catalog} book/currency + + + Here is an another example where Json array's values are updated and then read from it: + + .. code:: robotframework + + *** Settings *** + + Library HttpCtrl.Client + Library HttpCtrl.Json + + *** Test Cases *** + + Write And Read Json Array Value + ${json template}= Catenate + ... { + ... "array": [ + ... "red", "green", "blue", "yellow" + ... ] + ... } + + ${colors}= Set Json Value In String ${json template} array/3 white + + ${red}= Get Json Value From String ${colors} array/0 + ${green}= Get Json Value From String ${colors} array/1 + ${blue}= Get Json Value From String ${colors} array/2 + ${white}= Get Json Value From String ${colors} array/3 + + """ + + @staticmethod + def get_json_value_from_string(json_string, path): + """ + + Return value from Json that is represented by string. Be aware, returned value's type corresponds to its + type in Json. + + `json_string` [in] (string): Json that is represented by string. + + `path` [in] (string): Path to Json node value. + + Example how to obtain Json value: + + +-----------+----------------------------+------------------------------------+------------+ + | ${value}= | Get Json Value From String | { "book": { "title": "Unknown" } } | book/title | + +-----------+----------------------------+------------------------------------+------------+ + + .. code:: text + + ${json}= Catenate + ... { + ... "book": { + ... "title": "Unknown" + ... } + ... } + ${value}= Get Json Value From String ${json} book/title + + """ + json_content = json.loads(json_string) + keys = path.split('/') + + current_element = json_content + for key in keys: + if isinstance(current_element, list): + key = int(key) + + current_element = current_element[key] + + return current_element + + + @staticmethod + def set_json_value_in_string(json_string, path, value): + """ + + Set value in Json that is represented by string. Be aware type of updated value should correspond to its + type in Json, otherwise type will be changed with new value. + + `json_string` [in] (string): Json that is represented by string. + + `path` [in] (string): Path to Json node value. + + `value` [in] (any): New value for the Json node. + + Example how to set Json value: + + +-----------+--------------------------+------------------------------------+------------+--------+ + | ${value}= | Set Json Value In String | { "book": { "title": "Unknown" } } | book/title | "Math" | + +-----------+--------------------------+------------------------------------+------------+--------+ + + .. code:: text + + ${json}= Catenate + ... { + ... "book": { + ... "title": "Unknown" + ... } + ... } + ${value}= Set Json Value In String ${json} book/title "Math" + + Example how to set value in Json array node: + + +-----------+--------------------------+--------------------------------------+---------+---------+ + | ${value}= | Set Json Value In String | { "array": [ "one", "two", "ten" ] } | array/2 | "three" | + +-----------+--------------------------+--------------------------------------+---------+---------+ + + .. code:: text + + ${json}= Catenate + ... { + ... "array": [ + ... "one", "two", "ten" + ... ] + ... } + ${value}= Set Json Value In String ${json} array/2 "three" + + """ + json_content = json.loads(json_string) + keys = path.split('/') + + current_element = json_content + for key in keys: + if key == keys[-1]: + if isinstance(current_element, list): + key = int(key) + + current_element[key] = value + else: + if isinstance(current_element, list): + key = int(key) + + current_element = current_element[key] + + return json.dumps(json_content) diff --git a/robot/lib/python3.8/site-packages/HttpCtrl/http_handler.py b/robot/lib/python3.8/site-packages/HttpCtrl/http_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..1c2a52312d6924aaba981be28c1c354be3a6b1f7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/HttpCtrl/http_handler.py @@ -0,0 +1,121 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2019 +Copyright: GNU Public License + +HttpCtrl is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +HttpCtrl is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +from http.server import SimpleHTTPRequestHandler +from robot.api import logger + +from HttpCtrl.internal_messages import TerminationRequest, IgnoreRequest +from HttpCtrl.request import Request +from HttpCtrl.request_storage import RequestStorage +from HttpCtrl.response_storage import ResponseStorage + + +class HttpHandler(SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + self.server_version = "HttpCtrl.Server/" + self.sys_version = "" + + SimpleHTTPRequestHandler.__init__(self, *args, **kwargs) + + + def do_GET(self): + self.__default_handler('GET') + + + def do_POST(self): + body = self.rfile.read(int(self.headers['Content-Length'])).decode('utf-8') + self.__default_handler('POST', body) + + + def do_PUT(self): + body = self.rfile.read(int(self.headers['Content-Length'])).decode('utf-8') + self.__default_handler('PUT', body) + + + def do_OPTIONS(self): + self.__default_handler('OPTIONS') + + + def do_HEAD(self): + self.__default_handler('HEAD') + + + def do_PATCH(self): + body = self.rfile.read(int(self.headers['Content-Length'])).decode('utf-8') + self.__default_handler('PATCH', body) + + + def do_DELETE(self): + self.__default_handler('DELETE') + + + def log_message(self, format, *args): + return + + + def log_error(self, format, *args): + return + + + def log_request(self, code='-', size='-'): + return + + + def __default_handler(self, method, body=None): + host, port = self.client_address[:2] + + logger.info("'%s' request is received from '%s:%s'." % (method, host, port)) + + request = Request(host, port, method, self.path, self.headers, body) + RequestStorage().push(request) + + response = ResponseStorage().pop() + if isinstance(response, TerminationRequest) or isinstance(response, IgnoreRequest): + return + + try: + self.__send_response(response) + except Exception as exception: + logger.info("Response was not sent to client due to reason: '%s'." % str(exception)) + + + def __send_response(self, response): + if response is None: + logger.error("Response is not provided for incoming request.") + return + + self.send_response(response.get_status(), response.get_body()) + + headers = response.get_headers() + for key, value in headers.items(): + self.send_header(key, value) + + body = None + if response.get_body() is not None: + body = response.get_body().encode("utf-8") + self.send_header('Content-Length', len(body)) + + self.end_headers() + + if body is not None: + self.wfile.write(body) diff --git a/robot/lib/python3.8/site-packages/HttpCtrl/http_server.py b/robot/lib/python3.8/site-packages/HttpCtrl/http_server.py new file mode 100644 index 0000000000000000000000000000000000000000..74e4086112b24344b35ae7c4b66dd17c3415c619 --- /dev/null +++ b/robot/lib/python3.8/site-packages/HttpCtrl/http_server.py @@ -0,0 +1,83 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2019 +Copyright: GNU Public License + +HttpCtrl is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +HttpCtrl is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +import threading + +from socketserver import TCPServer + +from HttpCtrl.http_handler import HttpHandler +from HttpCtrl.internal_messages import TerminationRequest +from HttpCtrl.response_storage import ResponseStorage + + +class HttpServer: + def __init__(self, host, port): + self.__host = host + self.__port = port + + self.__handler = None + self.__server = None + + self.__is_run_state = False + self.__cv_run = threading.Condition() + + + def __del__(self): + if self.__server is not None: + self.stop() + + + def start(self): + TCPServer.allow_reuse_address = True + + self.__handler = HttpHandler + self.__server = TCPServer((self.__host, self.__port), self.__handler) + + try: + with self.__cv_run: + self.__is_run_state = True + self.__cv_run.notify() + + self.__server.serve_forever() + + except Exception as exception: + self.stop() + raise exception + + + def wait_run_state(self): + with self.__cv_run: + while not self.__is_run_state: + self.__cv_run.wait() + + + def stop(self): + if self.__server is not None: + ResponseStorage().push(TerminationRequest()) + + self.__server.shutdown() + self.__server.server_close() + self.__server = None + + with self.__cv_run: + self.__is_run_state = False diff --git a/robot/lib/python3.8/site-packages/HttpCtrl/internal_messages.py b/robot/lib/python3.8/site-packages/HttpCtrl/internal_messages.py new file mode 100644 index 0000000000000000000000000000000000000000..4f4ad21c65a16ae8c5230e06e1c22314f6ca37ae --- /dev/null +++ b/robot/lib/python3.8/site-packages/HttpCtrl/internal_messages.py @@ -0,0 +1,30 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2019 +Copyright: GNU Public License + +HttpCtrl is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +HttpCtrl is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + + +class TerminationRequest: + pass + + +class IgnoreRequest: + pass diff --git a/robot/lib/python3.8/site-packages/HttpCtrl/request.py b/robot/lib/python3.8/site-packages/HttpCtrl/request.py new file mode 100644 index 0000000000000000000000000000000000000000..68e55c1e9150c5554b4a3542f37961c8469bf328 --- /dev/null +++ b/robot/lib/python3.8/site-packages/HttpCtrl/request.py @@ -0,0 +1,51 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2019 +Copyright: GNU Public License + +HttpCtrl is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +HttpCtrl is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +class Request: + def __init__(self, host, port, method, url, headers, body=None): + self.__endpoint = (host, port) + self.__method = method + self.__url = url + self.__body = body + self.__headers = headers + + def __copy__(self): + return Request(self.__endpoint[0], self.__endpoint[1], self.__method, self.__url, self.__headers, self.__body) + + def __str__(self): + return "%s %s\n%s" % (self.__method, self.__url, self.__body) + + def get_endpoint(self): + return self.__endpoint + + def get_method(self): + return self.__method + + def get_headers(self): + return self.__headers + + def get_url(self): + return self.__url + + def get_body(self): + return self.__body diff --git a/robot/lib/python3.8/site-packages/HttpCtrl/request_storage.py b/robot/lib/python3.8/site-packages/HttpCtrl/request_storage.py new file mode 100644 index 0000000000000000000000000000000000000000..3d47e4f9f57f670a8a60b298c11e74fe9c11c931 --- /dev/null +++ b/robot/lib/python3.8/site-packages/HttpCtrl/request_storage.py @@ -0,0 +1,62 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2019 +Copyright: GNU Public License + +HttpCtrl is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +HttpCtrl is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +import threading + +from robot.api import logger + +from HttpCtrl.utils.singleton import Singleton + + +class RequestStorage(metaclass=Singleton): + def __init__(self): + self.__request = None + self.__event_incoming = threading.Condition() + + + def __ready(self): + return self.__request is not None + + + def push(self, request): + with self.__event_incoming: + logger.info("Push request to the Request Storage: %s" % request) + self.__request = request + self.__event_incoming.notify() + + + def pop(self, timeout=5.0): + with self.__event_incoming: + if not self.__ready(): + result = self.__event_incoming.wait_for(self.__ready, timeout) + if result is False: + return None + + request = self.__request + self.__request = None + return request + + + def clear(self): + with self.__event_incoming: + self.__request = None diff --git a/robot/lib/python3.8/site-packages/HttpCtrl/response.py b/robot/lib/python3.8/site-packages/HttpCtrl/response.py new file mode 100644 index 0000000000000000000000000000000000000000..02c569a0d0a83bfe1111d35b710ba50b95a12193 --- /dev/null +++ b/robot/lib/python3.8/site-packages/HttpCtrl/response.py @@ -0,0 +1,46 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2019 +Copyright: GNU Public License + +HttpCtrl is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +HttpCtrl is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +class Response: + def __init__(self, status, body, headers): + self.__status = status + self.__body = body + self.__headers = headers + + def __str__(self): + if self.__body is None or len(self.__body) == 0: + return str(self.__status) + + return "%s\n%s" % (str(self.__status), self.__body) + + def __copy__(self): + return Response(self.__status, self.__body, self.__headers) + + def get_status(self): + return self.__status + + def get_body(self): + return self.__body + + def get_headers(self): + return self.__headers diff --git a/robot/lib/python3.8/site-packages/HttpCtrl/response_storage.py b/robot/lib/python3.8/site-packages/HttpCtrl/response_storage.py new file mode 100644 index 0000000000000000000000000000000000000000..bc1e0269a956c49f9e3ca018f4333e64708f5ff4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/HttpCtrl/response_storage.py @@ -0,0 +1,62 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2019 +Copyright: GNU Public License + +HttpCtrl is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +HttpCtrl is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +import threading + +from robot.api import logger + +from HttpCtrl.utils.singleton import Singleton + + +class ResponseStorage(metaclass=Singleton): + def __init__(self): + self.__response = None + self.__event_incoming = threading.Condition() + + + def __ready(self): + return self.__response is not None + + + def push(self, response): + with self.__event_incoming: + logger.info("Push response to the Response Storage: %s" % response) + self.__response = response + self.__event_incoming.notify() + + + def pop(self, timeout=5.0): + with self.__event_incoming: + if not self.__ready(): + result = self.__event_incoming.wait_for(self.__ready, timeout) + if result is False: + return None + + response = self.__response + self.__response = None + return response + + + def clear(self): + with self.__event_incoming: + self.__response = None diff --git a/robot/lib/python3.8/site-packages/HttpCtrl/utils/__init__.py b/robot/lib/python3.8/site-packages/HttpCtrl/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a90813f474f2e11a587024daea8f28e2e38285b1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/HttpCtrl/utils/__init__.py @@ -0,0 +1,22 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2019 +Copyright: GNU Public License + +HttpCtrl is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +HttpCtrl is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" diff --git a/robot/lib/python3.8/site-packages/HttpCtrl/utils/singleton.py b/robot/lib/python3.8/site-packages/HttpCtrl/utils/singleton.py new file mode 100644 index 0000000000000000000000000000000000000000..632d92a09464aa1e2b9c7c2bbff383838ca274f3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/HttpCtrl/utils/singleton.py @@ -0,0 +1,30 @@ +""" + +HttpCtrl library provides HTTP/HTTPS client and server API to Robot Framework to make REST API testing easy. + +Authors: Andrei Novikov +Date: 2018-2019 +Copyright: GNU Public License + +HttpCtrl is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +HttpCtrl is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +class Singleton(type): + _instances = {} + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + + return cls._instances[cls] \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/JSONLibrary/JSONLibraryKeywords.py b/robot/lib/python3.8/site-packages/JSONLibrary/JSONLibraryKeywords.py new file mode 100644 index 0000000000000000000000000000000000000000..5f89ad4ed2772c04fc8df19bd2e84b63aabdc027 --- /dev/null +++ b/robot/lib/python3.8/site-packages/JSONLibrary/JSONLibraryKeywords.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +import json +import os.path +from robot.api import logger +from robot.api.deco import keyword +from jsonpath_rw import Index, Fields +from jsonpath_rw_ext import parse +from .version import VERSION + +__author__ = 'Traitanit Huangsri' +__email__ = 'traitanit.hua@gmail.com' +__version__ = VERSION + + +class JSONLibraryKeywords(object): + ROBOT_EXIT_ON_FAILURE = True + + @keyword('Load JSON From File') + def load_json_from_file(self, file_name): + """Load JSON from file. + + Return json as a dictionary object. + + Arguments: + - file_name: absolute json file name + + Return json object (list or dictionary) + + Examples: + | ${result}= | Load Json From File | /path/to/file.json | + """ + logger.debug("Check if file exists") + if os.path.isfile(file_name) is False: + logger.error("JSON file: " + file_name + " not found") + raise IOError + with open(file_name) as json_file: + data = json.load(json_file) + return data + + @keyword('Add Object To Json') + def add_object_to_json(self, json_object, json_path, object_to_add): + """Add an dictionary or list object to json object using json_path + + Arguments: + - json_object: json as a dictionary object. + - json_path: jsonpath expression + - object_to_add: dictionary or list object to add to json_object which is matched by json_path + + Return new json object. + + Examples: + | ${dict}= | Create Dictionary | latitude=13.1234 | longitude=130.1234 | + | ${json}= | Add Object To Json | ${json} | $..address | ${dict} | + """ + json_path_expr = parse(json_path) + for match in json_path_expr.find(json_object): + if type(match.value) is dict: + match.value.update(object_to_add) + if type(match.value) is list: + match.value.append(object_to_add) + + return json_object + + @keyword('Get Value From Json') + def get_value_from_json(self, json_object, json_path): + """Get Value From JSON using JSONPath + + Arguments: + - json_object: json as a dictionary object. + - json_path: jsonpath expression + + Return array of values + + Examples: + | ${values}= | Get Value From Json | ${json} | $..phone_number | + """ + json_path_expr = parse(json_path) + return [match.value for match in json_path_expr.find(json_object)] + + @keyword('Update Value To Json') + def update_value_to_json(self, json_object, json_path, new_value): + """Update value to JSON using JSONPath + + Arguments: + - json_object: json as a dictionary object. + - json_path: jsonpath expression + - new_value: value to update + + Return new json_object + + Examples: + | ${json_object}= | Update Value To Json | ${json} | $..address.streetAddress | Ratchadapisek Road | + """ + json_path_expr = parse(json_path) + for match in json_path_expr.find(json_object): + path = match.path + if isinstance(path, Index): + match.context.value[match.path.index] = new_value + elif isinstance(path, Fields): + match.context.value[match.path.fields[0]] = new_value + return json_object + + @keyword('Delete Object From Json') + def delete_object_from_json(self, json_object, json_path): + """Delete Object From JSON using json_path + + Arguments: + - json_object: json as a dictionary object. + - json_path: jsonpath expression + + Return new json_object + + Examples: + | ${json_object}= | Delete Object From Json | ${json} | $..address.streetAddress | + """ + json_path_expr = parse(json_path) + for match in json_path_expr.find(json_object): + path = match.path + if isinstance(path, Index): + del(match.context.value[match.path.index]) + elif isinstance(path, Fields): + del(match.context.value[match.path.fields[0]]) + return json_object + + @keyword('Convert JSON To String') + def convert_json_to_string(self, json_object): + """Convert JSON object to string + + Arguments: + - json_object: json as a dictionary object. + + Return new json_string + + Examples: + | ${json_str}= | Convert JSON To String | ${json_obj} | + """ + return json.dumps(json_object) + + @keyword('Convert String to JSON') + def convert_string_to_json(self, json_string): + """Convert String to JSON object + + Arguments: + - json_string: JSON string + + Return new json_object + + Examples: + | ${json_object}= | Convert String to JSON | ${json_string} | + """ + return json.loads(json_string) + diff --git a/robot/lib/python3.8/site-packages/JSONLibrary/__init__.py b/robot/lib/python3.8/site-packages/JSONLibrary/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8c23e2f0e8ee50cbe056998103e42f3dd18dae9b --- /dev/null +++ b/robot/lib/python3.8/site-packages/JSONLibrary/__init__.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +from .JSONLibraryKeywords import JSONLibraryKeywords +from .version import VERSION + +__author__ = 'Traitanit Huangsri' +__email__ = 'traitanit.hua@gmail.com' +__version__ = VERSION + + +class JSONLibrary(JSONLibraryKeywords): + """JSONLibrary is a robotframework testlibrary for manipulating JSON object (dictionary) + + You can get, add, update and delete your json object using JSONPath. + + == JSONPath Syntax == + | JSONPath | Description | + | $ | the root object/element | + | @ | the current object/element | + | . or [] | child operator | + | .. | recursive descent. JSONPath borrows this syntax from E4X | + | * | wildcard. All objects/element regardless their names. | + | [] | subscript operator. XPath uses it to iterate over element collections and for predicates. In Javascript and JSON it is the native array operator. | + | [,] | Union operator in XPath results in a combination of node sets. JSONPath allows alternate names or array indices as a set. | + | [start:end:step] | array slice operator borrowed from ES4 | + | ?() | applies a filter (script) expression. | + | () | script expression, using the underlying script engine. | + + == *** Known issue *** == + If there is a space in JSONPath expression, the module used by this library will throw an exception. + Therefore, please avoid the space in JSONPath expression if possible. + + *Example:* + | JSONPath | Exception? | + | $.[?(@.id == 1)] | Y | + | $.[?(@.id==1)] | N | + | $.[?(@.name=='test 123')] | N | + + == Example Test Cases == + | *** Settings *** | + | Library | JSONLibrary | + | | + | *** Test Cases *** | + | TestManipulatingJSON | + | ${json_object}= | Load JSON From File | example.json | + | ${object_to_add}= | Create Dictionary | country=Thailand | + | ${json_object}= | Add Object To Json | ${json_object} | $..address | ${object_to_add} | + | ${value}= | Get Value From Json | ${json_object} | $..country | + | Should Be Equal As Strings | ${value[0]} | Thailand | + + + """ + + ROBOT_LIBRARY_SCOPE = "GLOBAL" + ROBOT_LIBRARY_DOC_FORMAT = "ROBOT" diff --git a/robot/lib/python3.8/site-packages/JSONLibrary/version.py b/robot/lib/python3.8/site-packages/JSONLibrary/version.py new file mode 100644 index 0000000000000000000000000000000000000000..568646339977e1191d3f82644e82560c3684ed20 --- /dev/null +++ b/robot/lib/python3.8/site-packages/JSONLibrary/version.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +VERSION = "0.3.1" diff --git a/robot/lib/python3.8/site-packages/JSONSchemaLibrary/__init__.py b/robot/lib/python3.8/site-packages/JSONSchemaLibrary/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2bf9d0cf55eced219d968bdf0f7172d6cacfc466 --- /dev/null +++ b/robot/lib/python3.8/site-packages/JSONSchemaLibrary/__init__.py @@ -0,0 +1,37 @@ +from robot.api import logger +from robot.libraries.BuiltIn import BuiltIn + +import jsonschema +import os +import json + +__version__ = '1.0' + +ROBOT_LIBRARY_DOC_FORMAT = 'reST' + +class JSONSchemaLibrary: + """JSONSchemaLibrary is a library to validate JSON against JSON Schema definitions using Robot Framework. + + It uses the jsonschema library for Python: https://github.com/Julian/jsonschema + + *Before running tests* + + Prior to running JSONSchemaLibrary tests, you must make sure that the JSON Schema definitions are available + somewhere on the local filesystem. The default is to look in a subdirectory called `schemas`. + """ + ROBOT_LIBRARY_SCOPE = 'Global' + + def __init__(self, schema_location='schemas'): + self.schema_location = schema_location + if not self.schema_location.endswith('/'): + self.schema_location = '{}/'.format(self.schema_location) + + def validate_json(self, schema_filename, sample): + """Validates the sample JSON against the given schema.""" + schema = json.loads(open('{}/{}'.format(self.schema_location, schema_filename)).read()) + resolver = jsonschema.RefResolver('file://{}'.format(self.schema_location), schema) + try: + jsonschema.validate(sample, schema, resolver=resolver) + except jsonschema.ValidationError as e: + logger.debug(e) + raise jsonschema.ValidationError('Validation error for schema {}: {}'.format(schema_filename, e.message)) diff --git a/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/LICENSE b/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..3d82c281ee8ceb45368153139f5935bd8d39c816 --- /dev/null +++ b/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2017-2020 Ingy döt Net +Copyright (c) 2006-2016 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/METADATA b/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..a70dd202d485af74ab22294dfbc7710364d0b127 --- /dev/null +++ b/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/METADATA @@ -0,0 +1,41 @@ +Metadata-Version: 2.1 +Name: PyYAML +Version: 5.3.1 +Summary: YAML parser and emitter for Python +Home-page: https://github.com/yaml/pyyaml +Author: Kirill Simonov +Author-email: xi@resolvent.net +License: MIT +Download-URL: https://pypi.org/project/PyYAML/ +Platform: Any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Cython +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Text Processing :: Markup +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* + +YAML is a data serialization format designed for human readability +and interaction with scripting languages. PyYAML is a YAML parser +and emitter for Python. + +PyYAML features a complete YAML 1.1 parser, Unicode support, pickle +support, capable extension API, and sensible error messages. PyYAML +supports standard YAML tags and provides Python-specific tags that +allow to represent an arbitrary Python object. + +PyYAML is applicable for a broad range of tasks from complex +configuration files to object serialization and persistence. + diff --git a/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/RECORD b/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..cae61d9785c50e2d66319fde9ccc891f7f960354 --- /dev/null +++ b/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/RECORD @@ -0,0 +1,40 @@ +PyYAML-5.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +PyYAML-5.3.1.dist-info/LICENSE,sha256=xAESRJ8lS5dTBFklJIMT6ScO-jbSJrItgtTMbEPFfyk,1101 +PyYAML-5.3.1.dist-info/METADATA,sha256=xTsZFjd8T4M-5rC2M3BHgx_KTTpEPy5vFDIXrbzRXPQ,1758 +PyYAML-5.3.1.dist-info/RECORD,, +PyYAML-5.3.1.dist-info/WHEEL,sha256=TpFVeXF_cAlV118WSIPWtjqW7nPvzoOw-49FmS3fDKQ,103 +PyYAML-5.3.1.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11 +yaml/__init__.py,sha256=XFUNbKTg4afAd0BETjGQ1mKQ97_g5jbE1C0WoKc74dc,13170 +yaml/__pycache__/__init__.cpython-38.pyc,, +yaml/__pycache__/composer.cpython-38.pyc,, +yaml/__pycache__/constructor.cpython-38.pyc,, +yaml/__pycache__/cyaml.cpython-38.pyc,, +yaml/__pycache__/dumper.cpython-38.pyc,, +yaml/__pycache__/emitter.cpython-38.pyc,, +yaml/__pycache__/error.cpython-38.pyc,, +yaml/__pycache__/events.cpython-38.pyc,, +yaml/__pycache__/loader.cpython-38.pyc,, +yaml/__pycache__/nodes.cpython-38.pyc,, +yaml/__pycache__/parser.cpython-38.pyc,, +yaml/__pycache__/reader.cpython-38.pyc,, +yaml/__pycache__/representer.cpython-38.pyc,, +yaml/__pycache__/resolver.cpython-38.pyc,, +yaml/__pycache__/scanner.cpython-38.pyc,, +yaml/__pycache__/serializer.cpython-38.pyc,, +yaml/__pycache__/tokens.cpython-38.pyc,, +yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883 +yaml/constructor.py,sha256=O3Uaf0_J_5GQBoeI9ZNhpJAhtdagr_X2HzDgGbZOMnw,28627 +yaml/cyaml.py,sha256=LiMkvchNonfoy1F6ec9L2BiUz3r0bwF4hympASJX1Ic,3846 +yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837 +yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006 +yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533 +yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445 +yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061 +yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440 +yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495 +yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794 +yaml/representer.py,sha256=82UM3ZxUQKqsKAF4ltWOxCS6jGPIFtXpGs7mvqyv4Xs,14184 +yaml/resolver.py,sha256=DJCjpQr8YQCEYYjKEYqTl0GrsZil2H4aFOI9b0Oe-U4,8970 +yaml/scanner.py,sha256=KeQIKGNlSyPE8QDwionHxy9CgbqE5teJEz05FR9-nAg,51277 +yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165 +yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573 diff --git a/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/WHEEL b/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..d193dea988d97c2f7f7bf3c4fc196496d361cd4d --- /dev/null +++ b/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: false +Tag: cp38-cp38-linux_x86_64 + diff --git a/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..e6475e911f628412049bc4090d86f23ac403adde --- /dev/null +++ b/robot/lib/python3.8/site-packages/PyYAML-5.3.1.dist-info/top_level.txt @@ -0,0 +1,2 @@ +_yaml +yaml diff --git a/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/AUTHORS b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/AUTHORS new file mode 100644 index 0000000000000000000000000000000000000000..a5c9fb5030ff6496d36d3a71e0987a73516d694d --- /dev/null +++ b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/AUTHORS @@ -0,0 +1,239 @@ +Pygments is written and maintained by Georg Brandl . + +Major developers are Tim Hatch and Armin Ronacher +. + +Other contributors, listed alphabetically, are: + +* Sam Aaron -- Ioke lexer +* Ali Afshar -- image formatter +* Thomas Aglassinger -- Easytrieve, JCL, Rexx, Transact-SQL and VBScript + lexers +* Muthiah Annamalai -- Ezhil lexer +* Kumar Appaiah -- Debian control lexer +* Andreas Amann -- AppleScript lexer +* Timothy Armstrong -- Dart lexer fixes +* Jeffrey Arnold -- R/S, Rd, BUGS, Jags, and Stan lexers +* Jeremy Ashkenas -- CoffeeScript lexer +* José Joaquín Atria -- Praat lexer +* Stefan Matthias Aust -- Smalltalk lexer +* Lucas Bajolet -- Nit lexer +* Ben Bangert -- Mako lexers +* Max Battcher -- Darcs patch lexer +* Thomas Baruchel -- APL lexer +* Tim Baumann -- (Literate) Agda lexer +* Paul Baumgart, 280 North, Inc. -- Objective-J lexer +* Michael Bayer -- Myghty lexers +* Thomas Beale -- Archetype lexers +* John Benediktsson -- Factor lexer +* Trevor Bergeron -- mIRC formatter +* Vincent Bernat -- LessCSS lexer +* Christopher Bertels -- Fancy lexer +* Sébastien Bigaret -- QVT Operational lexer +* Jarrett Billingsley -- MiniD lexer +* Adam Blinkinsop -- Haskell, Redcode lexers +* Stéphane Blondon -- SGF and Sieve lexers +* Frits van Bommel -- assembler lexers +* Pierre Bourdon -- bugfixes +* Martijn Braam -- Kernel log lexer, BARE lexer +* Matthias Bussonnier -- ANSI style handling for terminal-256 formatter +* chebee7i -- Python traceback lexer improvements +* Hiram Chirino -- Scaml and Jade lexers +* Mauricio Caceres -- SAS and Stata lexers. +* Ian Cooper -- VGL lexer +* David Corbett -- Inform, Jasmin, JSGF, Snowball, and TADS 3 lexers +* Leaf Corcoran -- MoonScript lexer +* Christopher Creutzig -- MuPAD lexer +* Daniël W. Crompton -- Pike lexer +* Pete Curry -- bugfixes +* Bryan Davis -- EBNF lexer +* Bruno Deferrari -- Shen lexer +* Giedrius Dubinskas -- HTML formatter improvements +* Owen Durni -- Haxe lexer +* Alexander Dutton, Oxford University Computing Services -- SPARQL lexer +* James Edwards -- Terraform lexer +* Nick Efford -- Python 3 lexer +* Sven Efftinge -- Xtend lexer +* Artem Egorkine -- terminal256 formatter +* Matthew Fernandez -- CAmkES lexer +* Paweł Fertyk -- GDScript lexer, HTML formatter improvements +* Michael Ficarra -- CPSA lexer +* James H. Fisher -- PostScript lexer +* William S. Fulton -- SWIG lexer +* Carlos Galdino -- Elixir and Elixir Console lexers +* Michael Galloy -- IDL lexer +* Naveen Garg -- Autohotkey lexer +* Simon Garnotel -- FreeFem++ lexer +* Laurent Gautier -- R/S lexer +* Alex Gaynor -- PyPy log lexer +* Richard Gerkin -- Igor Pro lexer +* Alain Gilbert -- TypeScript lexer +* Alex Gilding -- BlitzBasic lexer +* GitHub, Inc -- DASM16, Augeas, TOML, and Slash lexers +* Bertrand Goetzmann -- Groovy lexer +* Krzysiek Goj -- Scala lexer +* Rostyslav Golda -- FloScript lexer +* Andrey Golovizin -- BibTeX lexers +* Matt Good -- Genshi, Cheetah lexers +* Michał Górny -- vim modeline support +* Alex Gosse -- TrafficScript lexer +* Patrick Gotthardt -- PHP namespaces support +* Hubert Gruniaux -- C and C++ lexer improvements +* Olivier Guibe -- Asymptote lexer +* Phil Hagelberg -- Fennel lexer +* Florian Hahn -- Boogie lexer +* Martin Harriman -- SNOBOL lexer +* Matthew Harrison -- SVG formatter +* Steven Hazel -- Tcl lexer +* Dan Michael Heggø -- Turtle lexer +* Aslak Hellesøy -- Gherkin lexer +* Greg Hendershott -- Racket lexer +* Justin Hendrick -- ParaSail lexer +* Jordi Gutiérrez Hermoso -- Octave lexer +* David Hess, Fish Software, Inc. -- Objective-J lexer +* Ken Hilton -- Typographic Number Theory and Arrow lexers +* Varun Hiremath -- Debian control lexer +* Rob Hoelz -- Perl 6 lexer +* Doug Hogan -- Mscgen lexer +* Ben Hollis -- Mason lexer +* Max Horn -- GAP lexer +* Alastair Houghton -- Lexer inheritance facility +* Tim Howard -- BlitzMax lexer +* Dustin Howett -- Logos lexer +* Ivan Inozemtsev -- Fantom lexer +* Hiroaki Itoh -- Shell console rewrite, Lexers for PowerShell session, + MSDOS session, BC, WDiff +* Brian R. Jackson -- Tea lexer +* Christian Jann -- ShellSession lexer +* Dennis Kaarsemaker -- sources.list lexer +* Dmitri Kabak -- Inferno Limbo lexer +* Igor Kalnitsky -- vhdl lexer +* Colin Kennedy - USD lexer +* Alexander Kit -- MaskJS lexer +* Pekka Klärck -- Robot Framework lexer +* Gerwin Klein -- Isabelle lexer +* Eric Knibbe -- Lasso lexer +* Stepan Koltsov -- Clay lexer +* Adam Koprowski -- Opa lexer +* Benjamin Kowarsch -- Modula-2 lexer +* Domen Kožar -- Nix lexer +* Oleh Krekel -- Emacs Lisp lexer +* Alexander Kriegisch -- Kconfig and AspectJ lexers +* Marek Kubica -- Scheme lexer +* Jochen Kupperschmidt -- Markdown processor +* Gerd Kurzbach -- Modelica lexer +* Jon Larimer, Google Inc. -- Smali lexer +* Olov Lassus -- Dart lexer +* Matt Layman -- TAP lexer +* Kristian Lyngstøl -- Varnish lexers +* Sylvestre Ledru -- Scilab lexer +* Chee Sing Lee -- Flatline lexer +* Mark Lee -- Vala lexer +* Valentin Lorentz -- C++ lexer improvements +* Ben Mabey -- Gherkin lexer +* Angus MacArthur -- QML lexer +* Louis Mandel -- X10 lexer +* Louis Marchand -- Eiffel lexer +* Simone Margaritelli -- Hybris lexer +* Kirk McDonald -- D lexer +* Gordon McGregor -- SystemVerilog lexer +* Stephen McKamey -- Duel/JBST lexer +* Brian McKenna -- F# lexer +* Charles McLaughlin -- Puppet lexer +* Kurt McKee -- Tera Term macro lexer, PostgreSQL updates, MySQL overhaul +* Lukas Meuser -- BBCode formatter, Lua lexer +* Cat Miller -- Pig lexer +* Paul Miller -- LiveScript lexer +* Hong Minhee -- HTTP lexer +* Michael Mior -- Awk lexer +* Bruce Mitchener -- Dylan lexer rewrite +* Reuben Morais -- SourcePawn lexer +* Jon Morton -- Rust lexer +* Paulo Moura -- Logtalk lexer +* Mher Movsisyan -- DTD lexer +* Dejan Muhamedagic -- Crmsh lexer +* Ana Nelson -- Ragel, ANTLR, R console lexers +* Kurt Neufeld -- Markdown lexer +* Nam T. Nguyen -- Monokai style +* Jesper Noehr -- HTML formatter "anchorlinenos" +* Mike Nolta -- Julia lexer +* Avery Nortonsmith -- Pointless lexer +* Jonas Obrist -- BBCode lexer +* Edward O'Callaghan -- Cryptol lexer +* David Oliva -- Rebol lexer +* Pat Pannuto -- nesC lexer +* Jon Parise -- Protocol buffers and Thrift lexers +* Benjamin Peterson -- Test suite refactoring +* Ronny Pfannschmidt -- BBCode lexer +* Dominik Picheta -- Nimrod lexer +* Andrew Pinkham -- RTF Formatter Refactoring +* Clément Prévost -- UrbiScript lexer +* Tanner Prynn -- cmdline -x option and loading lexers from files +* Oleh Prypin -- Crystal lexer (based on Ruby lexer) +* Xidorn Quan -- Web IDL lexer +* Elias Rabel -- Fortran fixed form lexer +* raichoo -- Idris lexer +* Daniel Ramirez -- GDScript lexer +* Kashif Rasul -- CUDA lexer +* Nathan Reed -- HLSL lexer +* Justin Reidy -- MXML lexer +* Norman Richards -- JSON lexer +* Corey Richardson -- Rust lexer updates +* Lubomir Rintel -- GoodData MAQL and CL lexers +* Andre Roberge -- Tango style +* Georg Rollinger -- HSAIL lexer +* Michiel Roos -- TypoScript lexer +* Konrad Rudolph -- LaTeX formatter enhancements +* Mario Ruggier -- Evoque lexers +* Miikka Salminen -- Lovelace style, Hexdump lexer, lexer enhancements +* Stou Sandalski -- NumPy, FORTRAN, tcsh and XSLT lexers +* Matteo Sasso -- Common Lisp lexer +* Joe Schafer -- Ada lexer +* Max Schillinger -- TiddlyWiki5 lexer +* Ken Schutte -- Matlab lexers +* René Schwaiger -- Rainbow Dash style +* Sebastian Schweizer -- Whiley lexer +* Tassilo Schweyer -- Io, MOOCode lexers +* Pablo Seminario -- PromQL lexer +* Ted Shaw -- AutoIt lexer +* Joerg Sieker -- ABAP lexer +* Robert Simmons -- Standard ML lexer +* Kirill Simonov -- YAML lexer +* Corbin Simpson -- Monte lexer +* Alexander Smishlajev -- Visual FoxPro lexer +* Steve Spigarelli -- XQuery lexer +* Jerome St-Louis -- eC lexer +* Camil Staps -- Clean and NuSMV lexers; Solarized style +* James Strachan -- Kotlin lexer +* Tom Stuart -- Treetop lexer +* Colin Sullivan -- SuperCollider lexer +* Ben Swift -- Extempore lexer +* Edoardo Tenani -- Arduino lexer +* Tiberius Teng -- default style overhaul +* Jeremy Thurgood -- Erlang, Squid config lexers +* Brian Tiffin -- OpenCOBOL lexer +* Bob Tolbert -- Hy lexer +* Matthias Trute -- Forth lexer +* Erick Tryzelaar -- Felix lexer +* Alexander Udalov -- Kotlin lexer improvements +* Thomas Van Doren -- Chapel lexer +* Daniele Varrazzo -- PostgreSQL lexers +* Abe Voelker -- OpenEdge ABL lexer +* Pepijn de Vos -- HTML formatter CTags support +* Matthias Vallentin -- Bro lexer +* Benoît Vinot -- AMPL lexer +* Linh Vu Hong -- RSL lexer +* Nathan Weizenbaum -- Haml and Sass lexers +* Nathan Whetsell -- Csound lexers +* Dietmar Winkler -- Modelica lexer +* Nils Winter -- Smalltalk lexer +* Davy Wybiral -- Clojure lexer +* Whitney Young -- ObjectiveC lexer +* Diego Zamboni -- CFengine3 lexer +* Enrique Zamudio -- Ceylon lexer +* Alex Zimin -- Nemerle lexer +* Rob Zimmerman -- Kal lexer +* Vincent Zurczak -- Roboconf lexer +* 15b3 -- Image Formatter improvements + +Many thanks for all contributions! diff --git a/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/LICENSE b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e1b15663d9577517ee7a88524eac0db62e727b18 --- /dev/null +++ b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2006-2021 by the respective authors (see AUTHORS file). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/METADATA b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..3a289c59aa22f1a3d9b3269244af3753c7495497 --- /dev/null +++ b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/METADATA @@ -0,0 +1,49 @@ +Metadata-Version: 2.1 +Name: Pygments +Version: 2.7.4 +Summary: Pygments is a syntax highlighting package written in Python. +Home-page: https://pygments.org/ +Author: Georg Brandl +Author-email: georg@python.org +License: BSD License +Keywords: syntax highlighting +Platform: any +Classifier: License :: OSI Approved :: BSD License +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: End Users/Desktop +Classifier: Intended Audience :: System Administrators +Classifier: Development Status :: 6 - Mature +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Operating System :: OS Independent +Classifier: Topic :: Text Processing :: Filters +Classifier: Topic :: Utilities +Requires-Python: >=3.5 + + +Pygments +~~~~~~~~ + +Pygments is a syntax highlighting package written in Python. + +It is a generic syntax highlighter suitable for use in code hosting, forums, +wikis or other applications that need to prettify source code. Highlights +are: + +* a wide range of over 500 languages and other text formats is supported +* special attention is paid to details, increasing quality by a fair amount +* support for new languages and formats are added easily +* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image formats that PIL supports and ANSI sequences +* it is usable as a command-line tool and as a library + +:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. +:license: BSD, see LICENSE for details. + + diff --git a/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/RECORD b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..aa127649cc862e63daef0c06ee40071e81612eb2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/RECORD @@ -0,0 +1,481 @@ +../../../bin/pygmentize,sha256=79gwn-3DLvNlKIKekkcNrOY3178NSJvRIl7Hft0fA5A,259 +Pygments-2.7.4.dist-info/AUTHORS,sha256=H-tIwcCUct3-KMEDRyfzHLBP7IXqjDP4DmFWzYa0Jas,8855 +Pygments-2.7.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Pygments-2.7.4.dist-info/LICENSE,sha256=wBLPF6K6eRQpd8jMW7FJemdUAb95wsm5WnYE4t396Lg,1331 +Pygments-2.7.4.dist-info/METADATA,sha256=mHAgWEG_eFKYpefF0385BjDRDqtvYm-q4IOrxPDMoGg,1883 +Pygments-2.7.4.dist-info/RECORD,, +Pygments-2.7.4.dist-info/WHEEL,sha256=EVRjI69F5qVjm_YgqcTXPnTAv3BfSUr0WVAHuSP3Xoo,92 +Pygments-2.7.4.dist-info/entry_points.txt,sha256=NXt9BRDRv6tAfDwqKM0bDHrrxaIt2f1nxH9CwjyjSKc,54 +Pygments-2.7.4.dist-info/top_level.txt,sha256=RjKKqrVIStoebLHdbs0yZ2Lk4rS7cxGguXsLCYvZ2Ak,9 +pygments/__init__.py,sha256=iTfUXya8qz_gne116qnCJ2PeelknzRdy5WEGRQQg9TY,3036 +pygments/__main__.py,sha256=yB_OBjjmewBVnKf7xJ1uFznYFExljPSp3p0H28j9jfc,372 +pygments/__pycache__/__init__.cpython-38.pyc,, +pygments/__pycache__/__main__.cpython-38.pyc,, +pygments/__pycache__/cmdline.cpython-38.pyc,, +pygments/__pycache__/console.cpython-38.pyc,, +pygments/__pycache__/filter.cpython-38.pyc,, +pygments/__pycache__/formatter.cpython-38.pyc,, +pygments/__pycache__/lexer.cpython-38.pyc,, +pygments/__pycache__/modeline.cpython-38.pyc,, +pygments/__pycache__/plugin.cpython-38.pyc,, +pygments/__pycache__/regexopt.cpython-38.pyc,, +pygments/__pycache__/scanner.cpython-38.pyc,, +pygments/__pycache__/sphinxext.cpython-38.pyc,, +pygments/__pycache__/style.cpython-38.pyc,, +pygments/__pycache__/token.cpython-38.pyc,, +pygments/__pycache__/unistring.cpython-38.pyc,, +pygments/__pycache__/util.cpython-38.pyc,, +pygments/cmdline.py,sha256=VO66RRAuKYqJZzvTaTAk9dcE0GmBYU5Wm_8llDIkTO0,19638 +pygments/console.py,sha256=JTHteqXHkWdWFTOIip0fyTMxkEGixhkGVDt-Eg0b3OY,1721 +pygments/filter.py,sha256=jPJTtPV5x3Tu11y5fpNkqi72t1EmqlBdWfOuEMBXcLY,1962 +pygments/filters/__init__.py,sha256=PlbDyto-gdhkIKlpZ4u6dbgMBV1989flqyzkqxW9nmU,40268 +pygments/filters/__pycache__/__init__.cpython-38.pyc,, +pygments/formatter.py,sha256=JnywA5byouCIVG7EPuVpZ5wtuBIyq-kTRCpWw4XDWlc,2917 +pygments/formatters/__init__.py,sha256=UK4woqneGatgdOEbtjn6HXkFKC5BiM33objikVxuyDY,5107 +pygments/formatters/__pycache__/__init__.cpython-38.pyc,, +pygments/formatters/__pycache__/_mapping.cpython-38.pyc,, +pygments/formatters/__pycache__/bbcode.cpython-38.pyc,, +pygments/formatters/__pycache__/html.cpython-38.pyc,, +pygments/formatters/__pycache__/img.cpython-38.pyc,, +pygments/formatters/__pycache__/irc.cpython-38.pyc,, +pygments/formatters/__pycache__/latex.cpython-38.pyc,, +pygments/formatters/__pycache__/other.cpython-38.pyc,, +pygments/formatters/__pycache__/rtf.cpython-38.pyc,, +pygments/formatters/__pycache__/svg.cpython-38.pyc,, +pygments/formatters/__pycache__/terminal.cpython-38.pyc,, +pygments/formatters/__pycache__/terminal256.cpython-38.pyc,, +pygments/formatters/_mapping.py,sha256=oCxZLm0WR6CGpVtZ102Ge3a0AKruTAq-8oQUdV1bnDw,6175 +pygments/formatters/bbcode.py,sha256=4V70x1jHV_4KpTMRJOOKXFTNQmwqFLhy-NSPSawLvbE,3314 +pygments/formatters/html.py,sha256=0oRPXWRmO7F-W98Tird0BcglkFN6dF_Cem3Kml--nBw,33476 +pygments/formatters/img.py,sha256=df338_yE111VtEdoP31iIlu14nOfujrPfqmZqyJNf6s,21202 +pygments/formatters/irc.py,sha256=40speKA8XHsYHkb8uNrWDt2jcWS8_9vp9j1ERO5lf7o,5869 +pygments/formatters/latex.py,sha256=PHk8UhLnIvDyrmzqixEy15q4Rr9qtIUxKL-1iv7IMro,18900 +pygments/formatters/other.py,sha256=dQlNncxFCcC27OHyLSPR66pCHgQpR8PFieHjon5JVfw,5136 +pygments/formatters/rtf.py,sha256=NqkFxPAl11lEDSu8lTUUuQ48DirD3DSIHn9dJisH6fs,5014 +pygments/formatters/svg.py,sha256=UMBvGStnGhg4yX7we-9dqixyEJZnYOYa2uN9CZzMC68,7279 +pygments/formatters/terminal.py,sha256=ngku-UB8CgAdy_-9PGjg5IqeYc3DPoVB7zrvYSUDTE4,4662 +pygments/formatters/terminal256.py,sha256=vLQpXLqRrUaxOSvANQ3S41vsUBH-ojhfiJksWdbRIoo,11126 +pygments/lexer.py,sha256=oBsw_p_x-ZWLTOG_I-kZ5mSyL01S4JUVNk96ETN6eDk,31501 +pygments/lexers/__init__.py,sha256=IZO1NuRcOxiDNTgaezZ2rJ0MhJDan49XFvEV2EIPbJg,11283 +pygments/lexers/__pycache__/__init__.cpython-38.pyc,, +pygments/lexers/__pycache__/_asy_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_cl_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_cocoa_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_csound_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_lasso_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_lua_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_mapping.cpython-38.pyc,, +pygments/lexers/__pycache__/_mql_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_mysql_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_openedge_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_php_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_postgres_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_scilab_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_sourcemod_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_stan_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_stata_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_tsql_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_usd_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_vbscript_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/_vim_builtins.cpython-38.pyc,, +pygments/lexers/__pycache__/actionscript.cpython-38.pyc,, +pygments/lexers/__pycache__/agile.cpython-38.pyc,, +pygments/lexers/__pycache__/algebra.cpython-38.pyc,, +pygments/lexers/__pycache__/ambient.cpython-38.pyc,, +pygments/lexers/__pycache__/ampl.cpython-38.pyc,, +pygments/lexers/__pycache__/apl.cpython-38.pyc,, +pygments/lexers/__pycache__/archetype.cpython-38.pyc,, +pygments/lexers/__pycache__/arrow.cpython-38.pyc,, +pygments/lexers/__pycache__/asm.cpython-38.pyc,, +pygments/lexers/__pycache__/automation.cpython-38.pyc,, +pygments/lexers/__pycache__/bare.cpython-38.pyc,, +pygments/lexers/__pycache__/basic.cpython-38.pyc,, +pygments/lexers/__pycache__/bibtex.cpython-38.pyc,, +pygments/lexers/__pycache__/boa.cpython-38.pyc,, +pygments/lexers/__pycache__/business.cpython-38.pyc,, +pygments/lexers/__pycache__/c_cpp.cpython-38.pyc,, +pygments/lexers/__pycache__/c_like.cpython-38.pyc,, +pygments/lexers/__pycache__/capnproto.cpython-38.pyc,, +pygments/lexers/__pycache__/chapel.cpython-38.pyc,, +pygments/lexers/__pycache__/clean.cpython-38.pyc,, +pygments/lexers/__pycache__/compiled.cpython-38.pyc,, +pygments/lexers/__pycache__/configs.cpython-38.pyc,, +pygments/lexers/__pycache__/console.cpython-38.pyc,, +pygments/lexers/__pycache__/crystal.cpython-38.pyc,, +pygments/lexers/__pycache__/csound.cpython-38.pyc,, +pygments/lexers/__pycache__/css.cpython-38.pyc,, +pygments/lexers/__pycache__/d.cpython-38.pyc,, +pygments/lexers/__pycache__/dalvik.cpython-38.pyc,, +pygments/lexers/__pycache__/data.cpython-38.pyc,, +pygments/lexers/__pycache__/devicetree.cpython-38.pyc,, +pygments/lexers/__pycache__/diff.cpython-38.pyc,, +pygments/lexers/__pycache__/dotnet.cpython-38.pyc,, +pygments/lexers/__pycache__/dsls.cpython-38.pyc,, +pygments/lexers/__pycache__/dylan.cpython-38.pyc,, +pygments/lexers/__pycache__/ecl.cpython-38.pyc,, +pygments/lexers/__pycache__/eiffel.cpython-38.pyc,, +pygments/lexers/__pycache__/elm.cpython-38.pyc,, +pygments/lexers/__pycache__/email.cpython-38.pyc,, +pygments/lexers/__pycache__/erlang.cpython-38.pyc,, +pygments/lexers/__pycache__/esoteric.cpython-38.pyc,, +pygments/lexers/__pycache__/ezhil.cpython-38.pyc,, +pygments/lexers/__pycache__/factor.cpython-38.pyc,, +pygments/lexers/__pycache__/fantom.cpython-38.pyc,, +pygments/lexers/__pycache__/felix.cpython-38.pyc,, +pygments/lexers/__pycache__/floscript.cpython-38.pyc,, +pygments/lexers/__pycache__/forth.cpython-38.pyc,, +pygments/lexers/__pycache__/fortran.cpython-38.pyc,, +pygments/lexers/__pycache__/foxpro.cpython-38.pyc,, +pygments/lexers/__pycache__/freefem.cpython-38.pyc,, +pygments/lexers/__pycache__/functional.cpython-38.pyc,, +pygments/lexers/__pycache__/gdscript.cpython-38.pyc,, +pygments/lexers/__pycache__/go.cpython-38.pyc,, +pygments/lexers/__pycache__/grammar_notation.cpython-38.pyc,, +pygments/lexers/__pycache__/graph.cpython-38.pyc,, +pygments/lexers/__pycache__/graphics.cpython-38.pyc,, +pygments/lexers/__pycache__/haskell.cpython-38.pyc,, +pygments/lexers/__pycache__/haxe.cpython-38.pyc,, +pygments/lexers/__pycache__/hdl.cpython-38.pyc,, +pygments/lexers/__pycache__/hexdump.cpython-38.pyc,, +pygments/lexers/__pycache__/html.cpython-38.pyc,, +pygments/lexers/__pycache__/idl.cpython-38.pyc,, +pygments/lexers/__pycache__/igor.cpython-38.pyc,, +pygments/lexers/__pycache__/inferno.cpython-38.pyc,, +pygments/lexers/__pycache__/installers.cpython-38.pyc,, +pygments/lexers/__pycache__/int_fiction.cpython-38.pyc,, +pygments/lexers/__pycache__/iolang.cpython-38.pyc,, +pygments/lexers/__pycache__/j.cpython-38.pyc,, +pygments/lexers/__pycache__/javascript.cpython-38.pyc,, +pygments/lexers/__pycache__/julia.cpython-38.pyc,, +pygments/lexers/__pycache__/jvm.cpython-38.pyc,, +pygments/lexers/__pycache__/lisp.cpython-38.pyc,, +pygments/lexers/__pycache__/make.cpython-38.pyc,, +pygments/lexers/__pycache__/markup.cpython-38.pyc,, +pygments/lexers/__pycache__/math.cpython-38.pyc,, +pygments/lexers/__pycache__/matlab.cpython-38.pyc,, +pygments/lexers/__pycache__/mime.cpython-38.pyc,, +pygments/lexers/__pycache__/ml.cpython-38.pyc,, +pygments/lexers/__pycache__/modeling.cpython-38.pyc,, +pygments/lexers/__pycache__/modula2.cpython-38.pyc,, +pygments/lexers/__pycache__/monte.cpython-38.pyc,, +pygments/lexers/__pycache__/mosel.cpython-38.pyc,, +pygments/lexers/__pycache__/ncl.cpython-38.pyc,, +pygments/lexers/__pycache__/nimrod.cpython-38.pyc,, +pygments/lexers/__pycache__/nit.cpython-38.pyc,, +pygments/lexers/__pycache__/nix.cpython-38.pyc,, +pygments/lexers/__pycache__/oberon.cpython-38.pyc,, +pygments/lexers/__pycache__/objective.cpython-38.pyc,, +pygments/lexers/__pycache__/ooc.cpython-38.pyc,, +pygments/lexers/__pycache__/other.cpython-38.pyc,, +pygments/lexers/__pycache__/parasail.cpython-38.pyc,, +pygments/lexers/__pycache__/parsers.cpython-38.pyc,, +pygments/lexers/__pycache__/pascal.cpython-38.pyc,, +pygments/lexers/__pycache__/pawn.cpython-38.pyc,, +pygments/lexers/__pycache__/perl.cpython-38.pyc,, +pygments/lexers/__pycache__/php.cpython-38.pyc,, +pygments/lexers/__pycache__/pointless.cpython-38.pyc,, +pygments/lexers/__pycache__/pony.cpython-38.pyc,, +pygments/lexers/__pycache__/praat.cpython-38.pyc,, +pygments/lexers/__pycache__/prolog.cpython-38.pyc,, +pygments/lexers/__pycache__/promql.cpython-38.pyc,, +pygments/lexers/__pycache__/python.cpython-38.pyc,, +pygments/lexers/__pycache__/qvt.cpython-38.pyc,, +pygments/lexers/__pycache__/r.cpython-38.pyc,, +pygments/lexers/__pycache__/rdf.cpython-38.pyc,, +pygments/lexers/__pycache__/rebol.cpython-38.pyc,, +pygments/lexers/__pycache__/resource.cpython-38.pyc,, +pygments/lexers/__pycache__/ride.cpython-38.pyc,, +pygments/lexers/__pycache__/rnc.cpython-38.pyc,, +pygments/lexers/__pycache__/roboconf.cpython-38.pyc,, +pygments/lexers/__pycache__/robotframework.cpython-38.pyc,, +pygments/lexers/__pycache__/ruby.cpython-38.pyc,, +pygments/lexers/__pycache__/rust.cpython-38.pyc,, +pygments/lexers/__pycache__/sas.cpython-38.pyc,, +pygments/lexers/__pycache__/scdoc.cpython-38.pyc,, +pygments/lexers/__pycache__/scripting.cpython-38.pyc,, +pygments/lexers/__pycache__/sgf.cpython-38.pyc,, +pygments/lexers/__pycache__/shell.cpython-38.pyc,, +pygments/lexers/__pycache__/sieve.cpython-38.pyc,, +pygments/lexers/__pycache__/slash.cpython-38.pyc,, +pygments/lexers/__pycache__/smalltalk.cpython-38.pyc,, +pygments/lexers/__pycache__/smv.cpython-38.pyc,, +pygments/lexers/__pycache__/snobol.cpython-38.pyc,, +pygments/lexers/__pycache__/solidity.cpython-38.pyc,, +pygments/lexers/__pycache__/special.cpython-38.pyc,, +pygments/lexers/__pycache__/sql.cpython-38.pyc,, +pygments/lexers/__pycache__/stata.cpython-38.pyc,, +pygments/lexers/__pycache__/supercollider.cpython-38.pyc,, +pygments/lexers/__pycache__/tcl.cpython-38.pyc,, +pygments/lexers/__pycache__/templates.cpython-38.pyc,, +pygments/lexers/__pycache__/teraterm.cpython-38.pyc,, +pygments/lexers/__pycache__/testing.cpython-38.pyc,, +pygments/lexers/__pycache__/text.cpython-38.pyc,, +pygments/lexers/__pycache__/textedit.cpython-38.pyc,, +pygments/lexers/__pycache__/textfmts.cpython-38.pyc,, +pygments/lexers/__pycache__/theorem.cpython-38.pyc,, +pygments/lexers/__pycache__/tnt.cpython-38.pyc,, +pygments/lexers/__pycache__/trafficscript.cpython-38.pyc,, +pygments/lexers/__pycache__/typoscript.cpython-38.pyc,, +pygments/lexers/__pycache__/unicon.cpython-38.pyc,, +pygments/lexers/__pycache__/urbi.cpython-38.pyc,, +pygments/lexers/__pycache__/usd.cpython-38.pyc,, +pygments/lexers/__pycache__/varnish.cpython-38.pyc,, +pygments/lexers/__pycache__/verification.cpython-38.pyc,, +pygments/lexers/__pycache__/web.cpython-38.pyc,, +pygments/lexers/__pycache__/webidl.cpython-38.pyc,, +pygments/lexers/__pycache__/webmisc.cpython-38.pyc,, +pygments/lexers/__pycache__/whiley.cpython-38.pyc,, +pygments/lexers/__pycache__/x10.cpython-38.pyc,, +pygments/lexers/__pycache__/xorg.cpython-38.pyc,, +pygments/lexers/__pycache__/yang.cpython-38.pyc,, +pygments/lexers/__pycache__/zig.cpython-38.pyc,, +pygments/lexers/_asy_builtins.py,sha256=Dz8NROsiQm1LGO1XEi-mtOA-RTXHjg9KIE6dEhm2fbM,27311 +pygments/lexers/_cl_builtins.py,sha256=BHh-U0O5ZonGVhe_r5SHl9p4yiPq0gcsgGI3JwfO4A0,14018 +pygments/lexers/_cocoa_builtins.py,sha256=Ut2WjBcDbN2tlhM03VlZ3AJx2HFpmkyGCsIyApVbDMw,39962 +pygments/lexers/_csound_builtins.py,sha256=gR1Ny8vcB1L5NuIqBp4ByDP5FcbeyMuHwdo4Hq7P_u0,17881 +pygments/lexers/_lasso_builtins.py,sha256=tUU0g9S25F5M9joSNRA6pfhRbsKErZySxU_t7-8veDc,134534 +pygments/lexers/_lua_builtins.py,sha256=eeKr5sIdM2GTQNpDfq-7ruEGVoYRr3zjIZCFqfT-33w,8297 +pygments/lexers/_mapping.py,sha256=25ZVV4s5usNXp2_THGAWFVVeh6z5VcP-8IFp45pSDYw,60281 +pygments/lexers/_mql_builtins.py,sha256=EciHgX-FoIWjBUSVRyFmQGiOQJYxNiAp8G-olcHVMW0,24737 +pygments/lexers/_mysql_builtins.py,sha256=axUR5RAG7VX_37TPtTTal8WUK9vqc-aONcv8FJnfWwM,24517 +pygments/lexers/_openedge_builtins.py,sha256=SH1NCnbO3eNQBvIOQvaP3rUsXRa7oMDknFgqd-MHIcI,48362 +pygments/lexers/_php_builtins.py,sha256=i16Jnzu8x983ycdaZewaplnDmnjW7mmOI4cH4umMK9s,154365 +pygments/lexers/_postgres_builtins.py,sha256=38TUy-0MQcnomsmLo7oMNGUSBpck4U2FNIBuJi96Mi8,12208 +pygments/lexers/_scilab_builtins.py,sha256=Ufr7EueHEfb0xczFiQCkyXStTOhTWRRBR5RLVeH74m4,52401 +pygments/lexers/_sourcemod_builtins.py,sha256=_aBGBGKhtfVZBf8sti8al7cs22s1tXGTfRPbSaoOzjo,27074 +pygments/lexers/_stan_builtins.py,sha256=2pSyHEK91jlgG1RQLDi77l8Yk1MKvelPt9vUjDsrOts,10481 +pygments/lexers/_stata_builtins.py,sha256=4YVDD8hFOkyH30NHGJyc2kWfO-sEgIri0FfyyctHZs0,25228 +pygments/lexers/_tsql_builtins.py,sha256=SfjB_Bqv0YIklYxEdDrZe_KvOlPRrMM9a6P0eLmOaFI,15484 +pygments/lexers/_usd_builtins.py,sha256=tzUM0i3kiBprRYjQv-Sq2YM3zGyds6XIyRA9UrQTNHo,1682 +pygments/lexers/_vbscript_builtins.py,sha256=BU2S36T6z7QqFTyFWc1byRtdBjgCxgkJi9L8B0vdtTk,4249 +pygments/lexers/_vim_builtins.py,sha256=u8qKQG5_NLW1Zrw1ZLeEBTm2AKyJriopnV0WyCTMEDY,57090 +pygments/lexers/actionscript.py,sha256=jbi67PeGNpPdo2d0vDVga9yc4I8vz-S7w8Eyl8O2Go0,11477 +pygments/lexers/agile.py,sha256=IFm6Z6gBAmzVscCWFlKyC9RphI914d36UO7J-lUHzvA,900 +pygments/lexers/algebra.py,sha256=LhaEPiWEBMMJ2PdquCHVyX1PrmosuIcp-QjAF5tWjKk,7754 +pygments/lexers/ambient.py,sha256=Q1801NG1N3PYviHi9XNPREwGQZETVjHiLDXtfNNCx6Y,2563 +pygments/lexers/ampl.py,sha256=A5wHOF9h6-L3vA4sqi1nFATdvksWwK0cDoSnYIj__pA,4123 +pygments/lexers/apl.py,sha256=5DMzyp6cJLY1ehCV2OEFtJFDkqWXOrYS0WAed66XPvc,3272 +pygments/lexers/archetype.py,sha256=rU5eaEeX2uF3MVo3jdGdcyj-QLeQFV80YawqA14iXUI,11131 +pygments/lexers/arrow.py,sha256=jhKpkvAsBC0v9NwfPq7cRkvaVmq5RkrfkIrSEwkPNbE,3524 +pygments/lexers/asm.py,sha256=ZgB4FAGzCUrSKBs0tKZNJk_DUi2DPatNSS6lFA-2rao,39375 +pygments/lexers/automation.py,sha256=qdRQgF0j_J-JpXuLI5l7fKIEjZhx8KKZjq3kXL-6DuY,19640 +pygments/lexers/bare.py,sha256=5S4Y5ZMiHY5KEYVLZ35W0vqeRwHpk9ncUauK3i6DeMA,2909 +pygments/lexers/basic.py,sha256=MTA9twkG4IdX0UKOR49QLndodkFaauazBz8dlX4EJ7A,27620 +pygments/lexers/bibtex.py,sha256=_hHuRoANJ8iEyMMDT2pWfgy5lc7i_ygRZGeUUChv4og,4725 +pygments/lexers/boa.py,sha256=j-vbb0yJHQosjqd9uWhjc-TSC4q-GW0qSwG1F3aY4w8,3970 +pygments/lexers/business.py,sha256=JopE6AfTsKNazW4iH6GcRb-hxJJtiLXAMkAQnATB3ak,28005 +pygments/lexers/c_cpp.py,sha256=1qK4uOUxGKS7S0Mo7E6QNqt8vQb3gaL1uV-icy9JOrI,15068 +pygments/lexers/c_like.py,sha256=Rx2pwd_Cy3NN5X6g90gMH3FWxw1dVYtT5oIIeNEMyNY,24916 +pygments/lexers/capnproto.py,sha256=eCCR2kw9Iqjp227BJpBNgpUzKbViy5mj-3OPfQ0iqPc,2194 +pygments/lexers/chapel.py,sha256=7f7mr2RR-R4YXkgLbkdWp80SHKt8Vxh3kRjQWKSZ1SI,3945 +pygments/lexers/clean.py,sha256=EQMmQT_T1hFgqMltBJ6BR2kqIoIuAl1vtTBWH_Zi90k,6385 +pygments/lexers/compiled.py,sha256=zhK-eEFlYArHz9Olly8dQhfnupNRXfKxcwv3Cwqdmj8,1385 +pygments/lexers/configs.py,sha256=iKNncinWn9qOWC4FJT4ubrKNhFkArAYDC538TfBUQoY,33695 +pygments/lexers/console.py,sha256=jYUC4k2888G-WiLMKOOLsta-5nmHlxjUkrOTeoA_Rb8,4120 +pygments/lexers/crystal.py,sha256=YO7E_p5-M4z0QzirwUEYna3MxJrEAezUwq9ELnZWzNk,15707 +pygments/lexers/csound.py,sha256=mDqt4DzQloo-pdck9v-NyE-pjZP0qXogIJMJSU9Eiu4,16845 +pygments/lexers/css.py,sha256=Uf6nY5UePEMsk8za2NLX_piRlEe337J5psJz8TGKsuY,31666 +pygments/lexers/d.py,sha256=olTB17SGGDecCYjSWKSHixtGzzXdKO_R3l6ggT_nE7U,9698 +pygments/lexers/dalvik.py,sha256=HVfczjMkiocv4uMoJChP8wwYakYuaSCwHyM05XjL7V8,4420 +pygments/lexers/data.py,sha256=T2quXcTiG8cn_MnJEx1DmvQWlu-6B5hU-KDVwPQtxbY,23929 +pygments/lexers/devicetree.py,sha256=RFAJtsSVbEt4P1nrkrxLvUIdImKjozOWYUQ4g5KKTmc,3991 +pygments/lexers/diff.py,sha256=KJm66T4GVmAveT-0g1rBTCsIss6n2srRquBIe4rJrr0,4885 +pygments/lexers/dotnet.py,sha256=rw9mUPK-JO8S5n2WH4ClPYKMKeY--s1wlJeyJCjsAEY,27981 +pygments/lexers/dsls.py,sha256=Wz9T-2WeUYwjKJOIhCTCTJvJloQJXtntRN2gOfd7ORA,35849 +pygments/lexers/dylan.py,sha256=vvCEJ7kptU5l2N4jVtgJ-qNDvEVMRFh6JQ8BmPGwOpc,10334 +pygments/lexers/ecl.py,sha256=gFRmVl2LJjT5T08QUOG19UkO5PUBCkwKVTe9o1MmYow,6270 +pygments/lexers/eiffel.py,sha256=_SsZMf0azxNb-SDIwAghk4dc5RMSM2RGXwCziryiZ58,2482 +pygments/lexers/elm.py,sha256=cjRf1aKkRtPj03oXnOovDio8Et3b_xeetBj4TPPxAEw,3003 +pygments/lexers/email.py,sha256=oDvH0Fd98DO7h2WjJwadE1Z4bZbKFv0Qh6SB0KBGFtU,5118 +pygments/lexers/erlang.py,sha256=lLF9pX70fmv64D6xtyVOlcciiaCoD8A6qMbyblFweuA,18987 +pygments/lexers/esoteric.py,sha256=cysr7OpZESAs4e3nAuYW7W6fDRxtMZ9IIiPPydjZ9X4,10173 +pygments/lexers/ezhil.py,sha256=YRdDPkjGtRl2kJ0bX7jSPzT7I-mvvFygP-DTuy593p0,3343 +pygments/lexers/factor.py,sha256=r71AVo_g45dMUdMOImqmoIZu9dUWMFJS9rL4Cp_NyPY,17860 +pygments/lexers/fantom.py,sha256=o1Ubx-HZg6_GOUuJxl87RSgyqf7SNbbHIkFbgBBjFRI,9982 +pygments/lexers/felix.py,sha256=ox-hr5FG91mhk3xvGx55yXBmyc3GnPv9FxfzcUtvmt8,9408 +pygments/lexers/floscript.py,sha256=Cn-iFQsU_bK8rq0bQ0LX81vlkM6SCCkHM4Fo7Yxqdf4,2667 +pygments/lexers/forth.py,sha256=WFQN8sxakFfxL2aayMmMGX69GzunGHr9qHL_XSIpbKk,7142 +pygments/lexers/fortran.py,sha256=uh9qA5Y1yVU0pnOaCeq_xeZDZR5lUPsonnKJ4RjoL00,9897 +pygments/lexers/foxpro.py,sha256=7W6pt8D7vM3ZQAClBJYOaxF5jOj2qufDiaiTG8TASL4,26237 +pygments/lexers/freefem.py,sha256=OxHBxGlywc0gzjW6hKM7TiBgU9ZYFCgcNvDa8CCfZ6s,27086 +pygments/lexers/functional.py,sha256=eQC1DkUdpJEZ-LU-ce2akK6IPRQHalpaEnhG_KE-E20,698 +pygments/lexers/gdscript.py,sha256=wx3kjbESK7JXu0jVg9o0mMVknmyMniltn4pM9EsjFI8,11146 +pygments/lexers/go.py,sha256=Z_K2IAxKu8v4sR98ZchVMCJQcmdQ4VVAJGPMOXr3CAw,3707 +pygments/lexers/grammar_notation.py,sha256=dfB_8mjyEHpu74OEAJPTaIZRoZIL-7mPVhsyRULfqWI,7941 +pygments/lexers/graph.py,sha256=RZ8RrTttd8I569YtAXvR-gRhIhQWz8kNcCIilCLithA,2756 +pygments/lexers/graphics.py,sha256=YI4oesQZequWmYiFr9tbDAWs1wGz6dtyAVEvXNWFpTQ,38962 +pygments/lexers/haskell.py,sha256=SLSxc4uMtvprSPbZ2hnCGH5OFpFGSjXKlNe7vdlcpmI,32216 +pygments/lexers/haxe.py,sha256=hlYzs7pl7Jy_f7cKRhqJoPvoZa8JLb0oomU3j34rYsA,30970 +pygments/lexers/hdl.py,sha256=Y0e5MIjuq2p8MkOZ1ONdDQvHSxT7ENkB-YJJl2tuALk,22345 +pygments/lexers/hexdump.py,sha256=9sNXYcqLEigsDrBVUFuPqDwHZDiPumACm1ljh7ZVHWg,3507 +pygments/lexers/html.py,sha256=E5Nk_ObK_6p6Tj_PGdlcWypwL81LbDsaJ7ffKmTrz8I,20055 +pygments/lexers/idl.py,sha256=FbQob5xo0imh3NB_ySX3aOhqExRvkBvOZ7FzWPv_ByE,15248 +pygments/lexers/igor.py,sha256=KOM3J8Fp1BQ1NFJRq3lODPHCPMe0cnWAtm_DI8JEf74,30609 +pygments/lexers/inferno.py,sha256=HnwVNRnAiG8kjIqkegymuXzd9X7i-arewNRt5u6hTY4,3117 +pygments/lexers/installers.py,sha256=MnBnkaKJg8qiJ7NptULblfYkpjX7H21iLOX1MYNIPhQ,12866 +pygments/lexers/int_fiction.py,sha256=aj0A2MUdxZ2kq7N2H7YQX2KSrAbdj0DvbfvvWMpJFuc,56670 +pygments/lexers/iolang.py,sha256=vFxoCi8YX3T_UMLmzzARgzy49ygBabcvc-Ju5ijaPEU,1911 +pygments/lexers/j.py,sha256=n4hBEY-2yf-L5Nw-cHu3dy_vZyVw3b1J-UjS9efhZBU,4527 +pygments/lexers/javascript.py,sha256=eTptM2t87SZn4a__1jGtFY-5FWvxB3oHrs2fD-c0lgU,60795 +pygments/lexers/julia.py,sha256=Uz7S1J8Uhm5EdzR6fKT7W7tLU1luHKYKErCzL5Nl4YM,13877 +pygments/lexers/jvm.py,sha256=zMvX2tb_CyyJhWr3li3-hCidNdlCt97P6sKO7-rRuNs,70992 +pygments/lexers/lisp.py,sha256=ReanoJsXsfkpCqpPKt2V1FMCmcga8OiwPD9RzjsX3Nw,141367 +pygments/lexers/make.py,sha256=qwxJyoMQAVBHVtwoZmzPSvwjMzbKRIbfBiZSG2aDDrE,7416 +pygments/lexers/markup.py,sha256=MJqGcWDuI05hXgadctZIkVVRbk2jLVOtlTqA8zzYQp4,26749 +pygments/lexers/math.py,sha256=UiLGfJR_Yvkw6u0Vo9Eop2POy1pc6ipvSXoRUr-Ab0I,700 +pygments/lexers/matlab.py,sha256=ePvlKWeSt-RpWqdooHRNfqBPFTYpxXzf2syHvWMwWds,31896 +pygments/lexers/mime.py,sha256=iHlY7Qt_qPwGrB28OP-yzBYCg8PaCuMb7b--Nr6HLXc,7958 +pygments/lexers/ml.py,sha256=P6xj20ah8lZsz7wwKzXzmm4u2XyDz3-CdpaniGgOOEQ,35318 +pygments/lexers/modeling.py,sha256=m7g1XwMyUiyV10u1fp1oN_hLxCyvRXbXMK4BcPj5ED0,13408 +pygments/lexers/modula2.py,sha256=oF-0rLKdoUW0IVwbuxBhoKr0JTtJ2u97y-rd8GPbxxI,53090 +pygments/lexers/monte.py,sha256=J1GqBzY5JrJmPwa7jH6qS-wRuIi8SEw0qmHm74vvpmw,6307 +pygments/lexers/mosel.py,sha256=e-X2gAjNtPT6hekxNkQRnkepVkLMAdDCDiDUsFnGoaA,9211 +pygments/lexers/ncl.py,sha256=Tgu3ULC62_q_SrYDqDr4sLM9jTeserROXXP_k115yso,63986 +pygments/lexers/nimrod.py,sha256=OtSbpND8ZDaabuQ5gc_3BU8huOeEGYUNNUq5Hl_dQx0,5144 +pygments/lexers/nit.py,sha256=EluQOmOrQdNFrcv9SBYORXU38WrA-jmmb6Yn4dYJzMU,2743 +pygments/lexers/nix.py,sha256=Y4iaYtpmCSJPTJ2PqpGQQplkpOhgkS5hANRyZnzwVMg,4031 +pygments/lexers/oberon.py,sha256=BugAsLoAC1zozqR2GXW4O7ZnAxYW4fELLoucDtp7Wn0,4235 +pygments/lexers/objective.py,sha256=639Y_N_uj7VdWC4r2kiQBrv-ztHpAHAmPOvfd1OLuQI,22777 +pygments/lexers/ooc.py,sha256=trzgWNRdKDpqxwZZmQF5O9vtFttVnm0j7CoWLiDP27s,2999 +pygments/lexers/other.py,sha256=OPBgF1pEMY4tdjGLuKPn4oQk2UzRgiCnjK7x3p6_Dvk,1768 +pygments/lexers/parasail.py,sha256=ZpS1FLm4_SdeWF_Qr5mfE7MPHbYhfKRuMgQ3_QnP0Hc,2737 +pygments/lexers/parsers.py,sha256=eWOjVcr4IIpIL0HrjF1iLBKIb7XXyQDu1cS8dRK8SwE,25904 +pygments/lexers/pascal.py,sha256=vg8Zzw-a27dAnOjfSiFbYoNZ55rRht7vSvG0K7QW9yE,32632 +pygments/lexers/pawn.py,sha256=JedIRLQDibfc7qaQw91Qa8jXU8AJzdJBR8xtLpW3dVQ,8235 +pygments/lexers/perl.py,sha256=jt4WRPWNysgXzoKm-D1yVBtKWLO6E3I5Gmk8DOvyZ34,39103 +pygments/lexers/php.py,sha256=M3PhfK5319Je7G3diSklaewmzicL4kgmNaSangrq8p0,12571 +pygments/lexers/pointless.py,sha256=Xr4DprrPUTD6kxMoHIU9evrqN7Vwq4U-HzPAhn2VJIY,1992 +pygments/lexers/pony.py,sha256=U4nGuPzmMcqWiLTmsB3VoYxZL1voJBsz_P_tDRUeeaw,3268 +pygments/lexers/praat.py,sha256=alDEemfcr1ruN2l46YtbQACRh1751GU62oA13bQ3KgQ,12297 +pygments/lexers/prolog.py,sha256=ZHptP323bMNyX0R7HYisKfNKD4vgpJvpwUW54556_rM,12407 +pygments/lexers/promql.py,sha256=tbicJPtCrr4jQVb_YPBdJF2XAgblUNB9nESRc2_J79o,4757 +pygments/lexers/python.py,sha256=wiJYNcUnlQ-_9fehG-PUxxaSTveaSKSD2Vl2HqFhIhA,51124 +pygments/lexers/qvt.py,sha256=vE6uQk5sZ8tN4H-AYV-0oRRlHFL9yaCcb7hD2ToDI_Y,6096 +pygments/lexers/r.py,sha256=pJnBbK4pd3nuuHaNlgddy5AUQTxud9A74DvZ1hd-ZJY,6201 +pygments/lexers/rdf.py,sha256=yXxbhfmYVqaCHfK4pWzy-miZB8qYlMVTSwdNAx6eO7w,15814 +pygments/lexers/rebol.py,sha256=4wrD2Wgf_q0Alzw6ogRbS8mpbs4OUdDMBISS72N5Ru8,18624 +pygments/lexers/resource.py,sha256=Rq_jXtjhqIwagDYU5xUANWQhOij2ysxTkfEB1X-wTII,2926 +pygments/lexers/ride.py,sha256=0LmhZ7DN-e5UikiGknXjEBsjG3vqVboUwNxYbuBn3LU,5074 +pygments/lexers/rnc.py,sha256=bBU2Kk2B-EVjvE6Wo7eJREf-Qz-0SphXuioiNPScrw8,1990 +pygments/lexers/roboconf.py,sha256=gQMYlqUfjIbcxN9wnsuFXUHhKHjEb62m1GJIFbqPrNU,2070 +pygments/lexers/robotframework.py,sha256=kfPiQ7Fuz8OTvZc8QQ4IORrrPhIOr-dI_v08wVPtKg0,18436 +pygments/lexers/ruby.py,sha256=D3rFp50kxtmz9oUpNtmBa06SmhEOSbaMkRVytSEV2KA,22386 +pygments/lexers/rust.py,sha256=9KwxZdXGOoK3FiZnPlSCGAouvA_k8wzn0bfRbxp4vgQ,8235 +pygments/lexers/sas.py,sha256=4GX1z3US4_R-ctQ8O_w_yUTYH_iOOHyqyIcjHnfBqAA,9449 +pygments/lexers/scdoc.py,sha256=6HQ_HFwHhT_hV0CRHqJPkfZ6sRYJv6Zzh0jjJu2unOQ,2268 +pygments/lexers/scripting.py,sha256=_mRR5lMffS2-SmcRABNTqtUhOZu_Iamio8448zr8UIQ,70056 +pygments/lexers/sgf.py,sha256=HDIfDf0xq5rCB5Pm6T6oss2j_MJWPHSjtD4I_hzJRIU,2024 +pygments/lexers/shell.py,sha256=4irrOpp2UV3MPR5qRHHTvNP7nNHD2NE9yKDSQrwCWwM,35917 +pygments/lexers/sieve.py,sha256=hwZYYOTxN2qiPK5221EcfEbLeHP0cUJAtOh9Co45MMY,2313 +pygments/lexers/slash.py,sha256=-gOR22Wi2grvXaVbi3KqpVPeq1xQa6C6AZmbs82knl0,8506 +pygments/lexers/smalltalk.py,sha256=NPRJhLNYdQifY10l8MkgIGHc3r2Nkx6a6G6ZjRJoE28,7216 +pygments/lexers/smv.py,sha256=LyUqDYvVvTtPpf5LjYHTX6bDqLs4WGXdonfo9tbqW6M,2793 +pygments/lexers/snobol.py,sha256=PpfU0uCKGyfTItqMMwbOuuybZ8uAtKVpD0zm_R0Le28,2756 +pygments/lexers/solidity.py,sha256=alzOo_fEiej9vqTpSgylw4jQbFn5DLl6-ZjxbPi24jk,3195 +pygments/lexers/special.py,sha256=_9O_T3vWzs_dIqpa6Kr0fJt8uw0Ozll2oNE5rnWC3JM,3118 +pygments/lexers/sql.py,sha256=hh1norRmMFCY_gN9sLbyBVdJMth53iaBeXoVjx9dTQg,34033 +pygments/lexers/stata.py,sha256=4U9xOuoWn6PJ8tCDBK-g2es3E2BHqo3uJZ-UDZB8BgM,6438 +pygments/lexers/supercollider.py,sha256=ELWrqIBKQLo_OEn2xlIGsQqdIrt4lXCKEKtf0AB0mRQ,3717 +pygments/lexers/tcl.py,sha256=GKLoUbMLeW8RWTHsp53JHG-QbCM8xSnhE5q3fq7eMQA,5398 +pygments/lexers/templates.py,sha256=0fZqpovSyLxfrCr_5s757ion8zR7RRCLbhGXWoUsxTs,71583 +pygments/lexers/teraterm.py,sha256=ir-DzM2FoN-ZmJWrfAH9KgPInsC9MiX4DTIJT4IMgH0,9909 +pygments/lexers/testing.py,sha256=DUQyBOvocFaRJOtVxOViZbuxGeB5l3avRLUyVMYJ-7k,10749 +pygments/lexers/text.py,sha256=w5pOJ--WmClrG-AdzUv9vJx3Y4-Q6mkE5ElJiQWXuKE,1030 +pygments/lexers/textedit.py,sha256=XfsvjJU-zvqY5J_w_cmBvAVNxKmvd8HsoJ815R-SyII,6104 +pygments/lexers/textfmts.py,sha256=GVYZZzjEPzEbc0-WzVqXqnimY1X3Y5QPWbfCuZj-JzA,15182 +pygments/lexers/theorem.py,sha256=udDByEK9iNSDfh_w40LAJiGoxdD9bLdgoMSENtxT4do,19370 +pygments/lexers/tnt.py,sha256=oqVKwK7d_wDsqMRZmZaSLZHXl1PJr7d_cjOqKCZg-SU,10178 +pygments/lexers/trafficscript.py,sha256=p9igv7d9YUMzLv2IVLyyTRJUyKsXxS6jy5ry0Gwid0A,1546 +pygments/lexers/typoscript.py,sha256=5tsH5Jynys-4wdHKcDuIKnN-m7rCoR7yI7y85UWdm4c,8224 +pygments/lexers/unicon.py,sha256=ZHP1GtEDl-icS4ml45vTvaXljPG6FqaKQQ8YVt0SFJ8,18536 +pygments/lexers/urbi.py,sha256=uSv9rbrb3MMbzC7CgA42I8Co5VaMFQa2wUorZS_SLgI,6062 +pygments/lexers/usd.py,sha256=uBS7KweqR6KCktRKKnssgrQE035YphH0S6j5x8SELI0,3475 +pygments/lexers/varnish.py,sha256=X1oFH08ue8sZmMT9iYU52PUmbE6AMtZnMToCx0Iprzs,7267 +pygments/lexers/verification.py,sha256=pQSSpcan7MBkDGs00KuMblmoSRzNsbihnqpEvu4ipOY,3932 +pygments/lexers/web.py,sha256=lWG-ZuQqJxKmbDJb2zkgryokQiEcglZsqV6ksjJnQiQ,918 +pygments/lexers/webidl.py,sha256=v3sSry88WT7Vwvv1tS9b-5z2HyEXFs0k0og7nZx6IY4,10497 +pygments/lexers/webmisc.py,sha256=-DmL7mkm_4UnmKbizRnll8dLCulc-qXk58YQczA1i44,40020 +pygments/lexers/whiley.py,sha256=aYWSH6STM9HwgADcWBaW4ySskQDA8BkgxQDXXFZViNU,4011 +pygments/lexers/x10.py,sha256=bJm9kR3bpvV5_5yinS6S7Or0QRMgRwaolDYHBUvdD6I,1971 +pygments/lexers/xorg.py,sha256=MVE8xLJjj8bqRJaKXuiNm7G7dViRjdNaINSkJnz7GfI,889 +pygments/lexers/yang.py,sha256=dYw-kvHddsgW-3gdZNfWSA-cIne4-3IZxNXh2c_i9a0,4547 +pygments/lexers/zig.py,sha256=8pBABW3cxnFagsfARIDl_EB5k1rQpDY0je0BpkCJPsk,3963 +pygments/modeline.py,sha256=Lu9JNKOG4ShvEpIuTUI5nCYx-0JCWv4JoO1Lj5i3OHw,1010 +pygments/plugin.py,sha256=umZUL2KApL_kFErJS0E918Juz7sUmk5u2HHwyeJTtTo,1734 +pygments/regexopt.py,sha256=n1cdeqHE0mxYQBwaNV-cRBjFrhkSgXCKybmKRziQPsw,3094 +pygments/scanner.py,sha256=DVy35QxYWLwO-pYK15U8QJF-MhtvxpWJkG38QNpVMTg,3115 +pygments/sphinxext.py,sha256=92VuC9cX5BdUX3Ux6iD-PPjpJKr3IxA0wwBH9gcUNls,4618 +pygments/style.py,sha256=FEuydM5zZA_37NRfX-UutbKwMoCqQQyl08Z-A7xy_Do,6031 +pygments/styles/__init__.py,sha256=QqatFV4pJmylXcq_i1039UHnXAK7wgyLYfcN35IrR-Y,2872 +pygments/styles/__pycache__/__init__.cpython-38.pyc,, +pygments/styles/__pycache__/abap.cpython-38.pyc,, +pygments/styles/__pycache__/algol.cpython-38.pyc,, +pygments/styles/__pycache__/algol_nu.cpython-38.pyc,, +pygments/styles/__pycache__/arduino.cpython-38.pyc,, +pygments/styles/__pycache__/autumn.cpython-38.pyc,, +pygments/styles/__pycache__/borland.cpython-38.pyc,, +pygments/styles/__pycache__/bw.cpython-38.pyc,, +pygments/styles/__pycache__/colorful.cpython-38.pyc,, +pygments/styles/__pycache__/default.cpython-38.pyc,, +pygments/styles/__pycache__/emacs.cpython-38.pyc,, +pygments/styles/__pycache__/friendly.cpython-38.pyc,, +pygments/styles/__pycache__/fruity.cpython-38.pyc,, +pygments/styles/__pycache__/igor.cpython-38.pyc,, +pygments/styles/__pycache__/inkpot.cpython-38.pyc,, +pygments/styles/__pycache__/lovelace.cpython-38.pyc,, +pygments/styles/__pycache__/manni.cpython-38.pyc,, +pygments/styles/__pycache__/monokai.cpython-38.pyc,, +pygments/styles/__pycache__/murphy.cpython-38.pyc,, +pygments/styles/__pycache__/native.cpython-38.pyc,, +pygments/styles/__pycache__/paraiso_dark.cpython-38.pyc,, +pygments/styles/__pycache__/paraiso_light.cpython-38.pyc,, +pygments/styles/__pycache__/pastie.cpython-38.pyc,, +pygments/styles/__pycache__/perldoc.cpython-38.pyc,, +pygments/styles/__pycache__/rainbow_dash.cpython-38.pyc,, +pygments/styles/__pycache__/rrt.cpython-38.pyc,, +pygments/styles/__pycache__/sas.cpython-38.pyc,, +pygments/styles/__pycache__/solarized.cpython-38.pyc,, +pygments/styles/__pycache__/stata_dark.cpython-38.pyc,, +pygments/styles/__pycache__/stata_light.cpython-38.pyc,, +pygments/styles/__pycache__/tango.cpython-38.pyc,, +pygments/styles/__pycache__/trac.cpython-38.pyc,, +pygments/styles/__pycache__/vim.cpython-38.pyc,, +pygments/styles/__pycache__/vs.cpython-38.pyc,, +pygments/styles/__pycache__/xcode.cpython-38.pyc,, +pygments/styles/abap.py,sha256=esKwzNUMUrUVon_8LG1EhKgIlTZYrTJDYg_C7KuRwgE,751 +pygments/styles/algol.py,sha256=163TX9BTd9FE0DAyCkQSbTSyzACN4V-n2Pvr6A5lab4,2263 +pygments/styles/algol_nu.py,sha256=oIyOBkeQ7fOgmsmjSqN4QhrK7Fk3uYkbckO1beoU1Lg,2278 +pygments/styles/arduino.py,sha256=17JU5KoZt6VrYkxUYDhyEtXv-yqhiACkYHmXncSuVO0,4491 +pygments/styles/autumn.py,sha256=W7jVp9QGlHSMODWpa-WrYvsrTk161PvzATFFVi1_yO0,2144 +pygments/styles/borland.py,sha256=SzkwZlVBKBzBUefmdMwP6W6E66Zirh1LF_ogyoi-aYU,1562 +pygments/styles/bw.py,sha256=r2jkKNEPuyr6E9yY9pSPATBSXmzEb3c8k9avv5T-2oI,1355 +pygments/styles/colorful.py,sha256=gq-dvxASORBm5_I0vn5uCqhvXwAzHUt5rCuK3JUg1Tg,2778 +pygments/styles/default.py,sha256=oKY8tMny5_ge39yFV102Nkzj5QzTCqh65OnQ7ML1Ha4,2532 +pygments/styles/emacs.py,sha256=q76MjOIhUlsPqalvTcVG3I0d2JgPHUfA8qZb2aXesXk,2486 +pygments/styles/friendly.py,sha256=6STNoGcgr5j-3ffFZ9vWDwSvvCWSslOis0gg1IgIkds,2515 +pygments/styles/fruity.py,sha256=d0sfwSfigD0lxErWA5kEyuVDjvNyvyVxDKB3jGF_l-w,1298 +pygments/styles/igor.py,sha256=QViFrzg1za_5-RmBGkSpvdi9t25LXyMkm9sDRWOp5dc,739 +pygments/styles/inkpot.py,sha256=Ib72P5lzbhq3pilyMfd1HHnwoGS2U1Xi3o37k62ZKOo,2347 +pygments/styles/lovelace.py,sha256=AUkFhrTws5BhO595e77TWll-kvyGWvwZC6DAiUuhtw0,3173 +pygments/styles/manni.py,sha256=lPumugGWn48gyS_0O8-_h6bLGhpLh-_hDNEkhq49-FU,2374 +pygments/styles/monokai.py,sha256=m3-J2diLELAeLCXes3KCoWz8yLG-7qj6ouQOrkiJFGg,5086 +pygments/styles/murphy.py,sha256=xtBahsrV2EIUM5Zt4k0tbO6Yo80DEJb0VoVY4EH4vDE,2751 +pygments/styles/native.py,sha256=UY90nqN_dHJVfM683NxYL_k8K3W85fBoGAFEsIBmxZw,1938 +pygments/styles/paraiso_dark.py,sha256=Pfha2xg2IvUBvL9THqZWM1vlQmPM07iUNYnGI4xOvqI,5641 +pygments/styles/paraiso_light.py,sha256=Ue2yXyLGKB_Cmu4rhuJwqKrhCWpYOCUC1D2YLE4cfBY,5645 +pygments/styles/pastie.py,sha256=Ms3_Qh1AMw4llImeR4h6_9Mf0-91xq8A_q7P09ApYv8,2473 +pygments/styles/perldoc.py,sha256=W8Tq4wIDAJMZFyakdZDbr15JOV0XQ2YFFXfWaVw-ZW0,2175 +pygments/styles/rainbow_dash.py,sha256=hnIaeEUub1zqHQ2qH8fxZZAv4IryTV0XMaEPEv9ZILw,2480 +pygments/styles/rrt.py,sha256=gqljRiIqC9IQgNZo1tvYEipKtSiN47oAEpnyHinGmYw,852 +pygments/styles/sas.py,sha256=IaLLUCHkowJj1k8Bhwnm8MYoo1gbSh8zxM9aJFqb40Y,1441 +pygments/styles/solarized.py,sha256=ln4_45hl7MjTDBRxsSAZ5-sybipjBgh5tay5Qoc8zHM,3955 +pygments/styles/stata_dark.py,sha256=49DdeiR1TlMVo-uQxjCyfkY0LPmssZNEO2_wIitVgpE,1245 +pygments/styles/stata_light.py,sha256=q75PUX6GI4wIpAHgVFHdGOmGOQg29-2ad9TTG9ACuTE,1274 +pygments/styles/tango.py,sha256=NqAbK-zTaIwd0q0KshqicEYy54lN1hlpam2tscraK4g,7096 +pygments/styles/trac.py,sha256=k1KH6QKoIvbzrYX9HbBzewat_l2ZrZmz8WrmYUsrXN4,1933 +pygments/styles/vim.py,sha256=4AtPaXAvDTdihusqLs51DUnQxAoCGcU45KwZDO22yKs,1976 +pygments/styles/vs.py,sha256=Qcw9Dmh65xEQVtCX8LRTQDyTGcQBvxR9vlrc6NkhU0w,1073 +pygments/styles/xcode.py,sha256=Jyk9KJHmN3XP2Bfm6EPLJbO04NXm0aNlDf55TECzi2M,1501 +pygments/token.py,sha256=VCK5rR7nHmjk65ffsWdHzpMeiUMJM_N6bBBTXuIarVQ,6167 +pygments/unistring.py,sha256=bpoxJUR4yOweAYcL06C5C8ucUKPJdnI72HjHtsvhopg,63224 +pygments/util.py,sha256=APT-iLTcxi0mjHynSoSrt2RoW6foVjdKQKm9aejIEKc,9147 diff --git a/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/WHEEL b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..83ff02e961fce5ad7befce746ff02635e1616315 --- /dev/null +++ b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.35.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/entry_points.txt b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..756d801bdf597be8c60fcd698b3fce94c4fe3481 --- /dev/null +++ b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +pygmentize = pygments.cmdline:main + diff --git a/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..a9f49e01c866d33b6e09b083df69a3c79ea76bdc --- /dev/null +++ b/robot/lib/python3.8/site-packages/Pygments-2.7.4.dist-info/top_level.txt @@ -0,0 +1 @@ +pygments diff --git a/robot/lib/python3.8/site-packages/REST/__init__.py b/robot/lib/python3.8/site-packages/REST/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3e1588643d477ac6e257731bc44b3b5e60ccd736 --- /dev/null +++ b/robot/lib/python3.8/site-packages/REST/__init__.py @@ -0,0 +1,382 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018- Anssi Syrjäsalo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# For Python 2 +from __future__ import unicode_literals +from __future__ import division +from io import open +from .compat import IS_PYTHON_2, STRING_TYPES + +from json import dumps, load, loads +from os import path +from yaml import load as load_yaml, SafeLoader + +from pygments import highlight, lexers, formatters +from requests.packages.urllib3 import disable_warnings + +if IS_PYTHON_2: + from urlparse import parse_qs, urljoin, urlparse +else: + from urllib.parse import parse_qs, urljoin, urlparse + +from robot.api import logger + +from .keywords import Keywords +from .version import __version__ + + +class REST(Keywords): + """RESTinstance is revolutionary and peaceful HTTP JSON API test library. + + It guides you to write more stable API tests relying on constraints (e.g. "people's email addresses must be valid"), rather than on values having the nature of change (e.g "the first created user's email is expected to stay foo@bar.com"). + + It bases on observations that we are more often concerned whether and which properties changed, when they should (not) had as this signals a bug, and less often what the actual values were/are. This also helps in tackling tests failing only because the test environment data was (not) reset. + + These resolutions in mind, it walks the path with you to contract-driven testing, from automatically generated JSON Schema data models to having formal OpenAPI service specs, as the both are essentially of the same origin. Both are also supported by major forces (Google, Microsoft, IBM et al. founded OpenAPI Initiative), and are language-agnostic which goes as well with Robot Framework. + + Contracts represent a common language within software teams, recognising our differing talents as designers, developers and test experts; or perhaps your new business companion wants to integrate to the system as well, but there is still some noise in signal - yet you don't feel very content providing them the source code, let alone explaining technical details, like which JSON properties are must in the response body, over the phone. + + Rest your mind. OSS got your back. + + + = Tutorial = + + There is a [https://github.com/asyrjasalo/RESTinstance/blob/master/examples/README.rst|step-by-step tutorial] in the making on using the library. + + For RESTful APIs, this library is intended to be used so that a test suite + is dedicated per endpoint. The test suite is divided into test cases so that + the differing operations (implemented by the endpoint via HTTP methods) + are tested with separate test cases. + + + = The state = + + The library represents its own state as JSON itself, as an array of objects. + Together these objects are commonly called instances. + + A single instance always has these three properties: + + - Request data as a JSON object + - Response data as a JSON object + - JSON Schema for the above two properties + + For each request-response, as soon as the response has been gotten + (and the request did not timeout), a new instance is created with these + properties. + + Request and response schemas are inferred if they are not already + expected by using expectation keywords. All the validations the library + implements are based on JSON Schema [http://json-schema.org/draft-07/json-schema-validation.html|draft-07] by default + but also [http://json-schema.org/draft-06/json-schema-validation.html|draft-06] and + [http://json-schema.org/draft-04/json-schema-validation.html|draft-04] can be configured. + + = The scope = + + All the assertion keywords, `Output` and `Output Schema` are effective + in the scope of the last instance. + + The scope of the library itself is test suite, meaning the instances + are persisted in memory until the execution of the test suite is finished, + regardless whether successfully or not. + + The last request and response is easiest seen with `Output`. + The output is written to terminal by default, as this is usually faster + for debugging purposes than finding the right keyword in ``log.html``. + + All instances can be output to a file with `RESTinstances` which can + be useful for additional logging. + """ + + ROBOT_LIBRARY_SCOPE = "TEST SUITE" + + # Altogether 24 keywords context: + # ------------------------------------------------------- + # 2 setting keywords next instances + # 3 expectation keywords next instances + # 7 operation keywords next instance + # 8 assertion keywords last instance's schema + # 4 I/O keywords the last instance or none + # ------------------------------------------------------- + + def __init__( + self, + url=None, + ssl_verify=True, + accept="application/json, */*", + content_type="application/json", + user_agent="RESTinstance/%s" % (__version__), + proxies={}, + schema={}, + spec={}, + instances=[], + ): + self.request = { + "method": None, + "url": None, + "scheme": "", + "netloc": "", + "path": "", + "query": {}, + "body": None, + "headers": { + "Accept": REST._input_string(accept), + "Content-Type": REST._input_string(content_type), + "User-Agent": REST._input_string(user_agent), + }, + "proxies": REST._input_object(proxies), + "timeout": [None, None], + "cert": None, + "sslVerify": REST._input_ssl_verify(ssl_verify), + "allowRedirects": True, + } + if url: + url = REST._input_string(url) + if url.endswith("/"): + url = url[:-1] + if not url.startswith(("http://", "https://")): + url = "http://" + url + url_parts = urlparse(url) + self.request["scheme"] = url_parts.scheme + self.request["netloc"] = url_parts.netloc + self.request["path"] = url_parts.path + if not self.request["sslVerify"]: + disable_warnings() + self.schema = { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": url, + "description": None, + "default": True, + "examples": [], + "type": "object", + "properties": { + "request": {"type": "object", "properties": {}}, + "response": {"type": "object", "properties": {}}, + }, + } + self.schema.update(self._input_object(schema)) + self.spec = {} + self.spec.update(self._input_object(spec)) + self.instances = self._input_array(instances) + + @staticmethod + def log_json(json, header="", also_console=True, sort_keys=False): + json = dumps( + json, + ensure_ascii=False, + indent=4, + separators=(",", ": "), + sort_keys=sort_keys, + ) + logger.info("%s\n%s" % (header, json)) # no coloring for log.html + if also_console: + json_data = highlight( + json, lexers.JsonLexer(), formatters.TerminalFormatter() + ) + logger.console("%s\n%s" % (header, json_data), newline=False) + return json + + @staticmethod + def _input_boolean(value): + if isinstance(value, (bool)): + return value + try: + json_value = loads(value) + if not isinstance(json_value, (bool)): + raise RuntimeError("Input is not a JSON boolean: %s" % (value)) + except ValueError: + raise RuntimeError("Input is not valid JSON: %s" % (value)) + return json_value + + @staticmethod + def _input_integer(value): + if isinstance(value, (int)): + return value + try: + json_value = loads(value) + if not isinstance(json_value, (int)): + raise RuntimeError("Input is not a JSON integer: %s" % (value)) + except ValueError: + raise RuntimeError("Input is not valid JSON: %s" % (value)) + return json_value + + @staticmethod + def _input_number(value): + if isinstance(value, (float, int)): + return value + try: + json_value = loads(value) + if not isinstance(json_value, (float, int)): + raise RuntimeError("Input is not a JSON number: %s" % (value)) + except ValueError: + raise RuntimeError("Input is not valid JSON: %s" % (value)) + return json_value + + @staticmethod + def _input_string(value): + if value == "": + return "" + if isinstance(value, STRING_TYPES): + if not value.startswith('"'): + value = '"' + value + if not value.endswith('"'): + value = value + '"' + try: + json_value = loads(value) + if not isinstance(json_value, STRING_TYPES): + raise RuntimeError("Input is not a JSON string: %s" % (value)) + except ValueError: + raise RuntimeError("Input not is valid JSON: %s" % (value)) + return json_value + + @staticmethod + def _input_object(value): + if isinstance(value, (dict)): + return value + try: + if path.isfile(value): + json_value = REST._input_json_from_file(value) + else: + json_value = loads(value) + if not isinstance(json_value, (dict)): + raise RuntimeError( + "Input or file has no JSON object: %s" % (value) + ) + except ValueError: + raise RuntimeError( + "Input is not valid JSON or a file: %s" % (value) + ) + return json_value + + @staticmethod + def _input_array(value): + if isinstance(value, (list)): + return value + try: + if path.isfile(value): + json_value = REST._input_json_from_file(value) + else: + json_value = loads(value) + if not isinstance(json_value, (list)): + raise RuntimeError( + "Input or file has no JSON array: %s" % (value) + ) + except ValueError: + raise RuntimeError( + "Input is not valid JSON or a file: %s" % (value) + ) + return json_value + + @staticmethod + def _input_json_from_file(path): + try: + with open(path, encoding="utf-8") as file: + return load(file) + except IOError as e: + raise RuntimeError("File '%s' cannot be opened:\n%s" % (path, e)) + except ValueError as e: + try: + with open(path, encoding="utf-8") as file: + return load_yaml(file, Loader=SafeLoader) + except ValueError: + raise RuntimeError( + "File '%s' is not valid JSON or YAML:\n%s" % (path, e) + ) + + @staticmethod + def _input_json_as_string(string): + return loads(string) + + @staticmethod + def _input_json_from_non_string(value): + try: + return REST._input_json_as_string(dumps(value, ensure_ascii=False)) + except ValueError: + raise RuntimeError("Input is not valid JSON: %s" % (value)) + + @staticmethod + def _input_client_cert(value): + if value is None or value == "null": + return None + if isinstance(value, STRING_TYPES): + return value + if isinstance(value, (list)): + if len(value) != 2: + raise RuntimeError( + "Client cert given as a (Python) list, " + + "must have length of 2: %s" % (value) + ) + return value + try: + value = loads(value) + if not isinstance(value, STRING_TYPES + (list)): + raise RuntimeError( + "Input is not a JSON string " + "or a list: %s" + (value) + ) + except ValueError: + raise RuntimeError( + "Input is not a JSON string " + "or an array: %s " % (value) + ) + if isinstance(value, (list)): + if len(value) != 2: + raise RuntimeError( + "Client cert given as a JSON array, " + + "must have length of 2: %s" % (value) + ) + return value + + @staticmethod + def _input_ssl_verify(value): + try: + return REST._input_boolean(value) + except RuntimeError: + value = REST._input_string(value) + if not path.isfile(value): + raise RuntimeError( + "SSL verify option is not " + + "a Python or a JSON boolean or a path to an existing " + + "CA bundle file: %s" % (value) + ) + return value + + @staticmethod + def _input_timeout(value): + if isinstance(value, (int, float)): + return [value, value] + if isinstance(value, (list)): + if len(value) != 2: + raise RuntimeError( + "Timeout given as a (Python) list, " + + "must have length of 2: %s" % (value) + ) + return value + try: + value = loads(value) + if not isinstance(value, (int, float, list)): + raise RuntimeError( + "Input is not a JSON integer, " + + "number or a list: %s" % (value) + ) + except ValueError: + raise RuntimeError("Input is not valid JSON: %s" % (value)) + if isinstance(value, (list)): + if len(value) != 2: + raise RuntimeError( + "Timeout given as a JSON array, " + + "must have length of 2: %s" % (value) + ) + else: + return value + return [value, value] diff --git a/robot/lib/python3.8/site-packages/REST/compat.py b/robot/lib/python3.8/site-packages/REST/compat.py new file mode 100644 index 0000000000000000000000000000000000000000..170c744357f8fd52bda4fcc5cb90d34a55b22076 --- /dev/null +++ b/robot/lib/python3.8/site-packages/REST/compat.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018- Anssi Syrjäsalo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +IS_PYTHON_2 = sys.version_info < (3,) + +if IS_PYTHON_2: + STRING_TYPES = (unicode, str) +else: + STRING_TYPES = str diff --git a/robot/lib/python3.8/site-packages/REST/keywords.py b/robot/lib/python3.8/site-packages/REST/keywords.py new file mode 100644 index 0000000000000000000000000000000000000000..fff4a7bf87e77ea6430b201fa78c4b592506a4eb --- /dev/null +++ b/robot/lib/python3.8/site-packages/REST/keywords.py @@ -0,0 +1,1530 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018- Anssi Syrjäsalo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# For Python 2 +from __future__ import unicode_literals +from __future__ import division +from io import open +from .compat import IS_PYTHON_2, STRING_TYPES + +from pytz import utc, UnknownTimeZoneError +from tzlocal import get_localzone + +from collections import OrderedDict +from copy import deepcopy +from datetime import datetime +from json import dumps, loads +from os import path, getcwd + +from flex.core import validate_api_call +from genson import SchemaBuilder +from jsonpath_ng.ext import parse as parse_jsonpath +from jsonschema import validate, FormatChecker +from jsonschema.exceptions import SchemaError, ValidationError +from requests import request as client +from requests.exceptions import SSLError, Timeout + +if IS_PYTHON_2: + from urlparse import parse_qsl, urljoin, urlparse +else: + from urllib.parse import parse_qsl, urljoin, urlparse + +from robot.api import logger +from robot.api.deco import keyword +from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError + +from .schema_keywords import SCHEMA_KEYWORDS + + +class Keywords(object): + def get_keyword_names(self): + return [ + name + for name in dir(self) + if hasattr(getattr(self, name), "robot_name") + ] + + ### Keywords start here + + @keyword(name=None, tags=("settings",)) + def set_client_cert(self, cert): + """*Sets the client cert for the requests.* + + The cert is either a path to a .pem file, or a JSON array, or a list + having the cert path and the key path. + + Values ``null`` and ``${None}`` can be used for clearing the cert. + + *Examples* + + | `Set Client Cert` | ${CURDIR}/client.pem | + | `Set Client Cert` | ["${CURDIR}/client.cert", "${CURDIR}/client.key"] | + | `Set Client Cert` | ${paths_list} | + """ + self.request["cert"] = self._input_client_cert(cert) + return self.request["cert"] + + @keyword(name=None, tags=("settings",)) + def set_headers(self, headers): + """*Sets new request headers or updates the existing.* + + ``headers``: The headers to add or update as a JSON object or a + dictionary. + + *Examples* + + | `Set Headers` | { "authorization": "Basic QWxhZGRpbjpPcGVuU2VzYW1"} | + | `Set Headers` | { "Accept-Encoding": "identity"} | + | `Set Headers` | ${auth_dict} | + """ + self.request["headers"].update(self._input_object(headers)) + return self.request["headers"] + + @keyword(name=None, tags=("expectations",)) + def expect_request(self, schema, merge=False): + """*Sets the schema to validate the request properties* + + Expectations are effective for following requests in the test suite, + or until they are reset or updated by using expectation keywords again. + On the test suite level (suite setup), they are best used for expecting + the endpoint wide properties that are common regardless of the tested + HTTP method, and on the test case level (test setup) to merge in + the HTTP method specific properties. + + `Expect Request` is intented to be used with tests that have some of the + request properties, e.g. body or query parameters, randomized ("fuzzing") + for validating that the sent values are within the expected scope. + + If the keyword is used, following HTTP keywords will fail when + their request properties are not valid against the expected schema. + + If the keyword is not used, a new schema is generated for each following + request for its ``body`` and ``query`` properties. Use `Output Schema` to output it and use it as an input to this keyword. + + *Options* + + ``merge``: Merges the new schema with the current instead of replacing it + + *Examples* + + | `Expect Request` | ${CURDIR}/valid_payload.json | | # See `Output Schema` | + | `Expect Request` | { "body": { "required": ["id"] } } | merge=true | + """ + schema = self._input_object(schema) + if "properties" not in schema: + schema = {"properties": schema} + if self._input_boolean(merge): + new_schema = SchemaBuilder(schema_uri=False) + new_schema.add_schema(self.schema["properties"]["request"]) + new_schema.add_schema(schema) + self.schema["properties"]["request"] = new_schema.to_schema() + else: + self.schema["properties"]["request"] = schema + return self.schema["properties"]["request"] + + @keyword(name=None, tags=("expectations",)) + def expect_response(self, schema, merge=False): + """*Sets the schema to validate the response properties.* + + Expectations are effective for following requests in the test suite, + or until they are reset or updated by using expectation keywords again. + On the test suite level (suite setup), they are best used for expecting + the endpoint wide properties that are common regardless of the tested + HTTP method, and on the test case level (test setup) to merge in + the HTTP method specific properties. + + `Expect Response` is intented to be used on the suite level to validate + the endpoint properties that hold regardless of the HTTP method, + such as body property types, responded headers, authentication, etc. + + If the keyword is used, following HTTP keywords will fail when + their response properties are not valid against the expected schema. + + If the keyword is not used, a new schema is inferred for each following + response for its ``body``. Use `Output Schema` to output it and use it + as an input to this keyword. + + *Options* + + ``merge``: Merges the new schema with the current instead of replacing it + + *Examples* + + | `Expect Response` | ${CURDIR}/endpoint_data_model.json | | # See `Output Schema` | + | `Expect Response` | { "headers": { "required": ["Via"] } } | merge=true | + | `Expect Response` | { "seconds": { "maximum": "1.0" } } | merge=true | + """ + schema = self._input_object(schema) + if "properties" not in schema: + schema = {"properties": schema} + if self._input_boolean(merge): + new_schema = SchemaBuilder(schema_uri=False) + new_schema.add_schema(self.schema["properties"]["response"]) + new_schema.add_schema(schema) + self.schema["properties"]["response"] = new_schema.to_schema() + else: + self.schema["properties"]["response"] = schema + return self.schema["properties"]["response"] + + @keyword(name=None, tags=("expectations",)) + def expect_response_body(self, schema): + """*Updates the schema to validate the response body properties.* + + Expectations are effective for following requests in the test suite, + or until they are reset or updated by using expectation keywords again. + On the test suite level (suite setup), they are best used for expecting + the endpoint wide properties that are common regardless of the tested + HTTP method, and on the test case level (test setup) to merge in + the HTTP method specific properties. + + `Expect Response Body` is intented to be used on the test case level, + to validate that the response body has the expected properties for + the particular HTTP method. Note that if something about response body + has been already expected with `Expected Response`, using this keyword + updates the expectations in terms of given response body properties. + + If the keyword is used, following HTTP keywords will fail if + their response body is not valid against the expected schema. + + If the keyword is not used, and no schema is already expected with + `Expect Response` for response ``body``, a new schema is inferred for it. + Use `Output Schema` to output it and use it as an input to this keyword. + + *Tips* + + Regardless whether the HTTP method returns one (an object) or many + (an array of objects), the validation of the object property types and features can usually be done to some extent on the test suite level + with `Expect Response`, then extended on the test case level using this + keyword. This helps in ensuring that the data model is unified between + the different HTTP methods. + + *Examples* + + | `Expect Response Body` | ${CURDIR}/user_properties.json | # See `Output Schema` | + | `Expect Response Body` | { "required": ["id", "token"] } | # Only these are required from this method | + | `Expect Response Body` | { "additionalProperties": false } | # Nothing extra should be responded by this method | + """ + response_properties = self.schema["properties"]["response"][ + "properties" + ] + if "body" in response_properties: + response_properties["body"].update(self._input_object(schema)) + else: + response_properties["body"] = self._input_object(schema) + return response_properties["body"] + + @keyword(name=None, tags=("expectations",)) + def clear_expectations(self): + """*Resets the expectations for both request and response.* + + Using this keyword resets any expectations set with keywords + `Expect Response`, `Expect Response Body` and `Expect Request`. + """ + self.schema["properties"]["request"] = { + "type": "object", + "properties": {}, + } + self.schema["properties"]["response"] = { + "type": "object", + "properties": {}, + } + return self.schema + + @keyword(name=None, tags=("http",)) + def head( + self, + endpoint, + timeout=None, + allow_redirects=None, + validate=True, + headers=None, + ): + """*Sends a HEAD request to the endpoint.* + + The endpoint is joined with the URL given on library init (if any). + If endpoint starts with ``http://`` or ``https://``, it is assumed + an URL outside the tested API (which may affect logging). + + *Options* + + ``timeout``: A number of seconds to wait for the response before failing the keyword. + + ``allow_redirects``: If true, follow all redirects. + In contrary to other methods, no HEAD redirects are followed by default. + + ``validate``: If false, skips any request and response validations set + by expectation keywords and a spec given on library init. + + ``headers``: The headers to add or override for this request. + + *Examples* + + | `HEAD` | /users/1 | + | `HEAD` | /users/1 | timeout=0.5 | + """ + endpoint = self._input_string(endpoint) + request = deepcopy(self.request) + request["method"] = "HEAD" + if allow_redirects is not None: + request["allowRedirects"] = self._input_boolean(allow_redirects) + if timeout is not None: + request["timeout"] = self._input_timeout(timeout) + validate = self._input_boolean(validate) + if headers: + request["headers"].update(self._input_object(headers)) + return self._request(endpoint, request, validate)["response"] + + @keyword(name=None, tags=("http",)) + def options( + self, + endpoint, + timeout=None, + allow_redirects=None, + validate=True, + headers=None, + ): + """*Sends an OPTIONS request to the endpoint.* + + The endpoint is joined with the URL given on library init (if any). + If endpoint starts with ``http://`` or ``https://``, it is assumed + an URL outside the tested API (which may affect logging). + + *Options* + + ``timeout``: A number of seconds to wait for the response before failing the keyword. + + ``allow_redirects``: If false, do not follow any redirects. + + ``validate``: If false, skips any request and response validations set + by expectation keywords and a spec given on library init. + + ``headers``: Headers as a JSON object to add or override for the request. + + *Examples* + + | `OPTIONS` | /users/1 | + | `OPTIONS` | /users/1 | allow_redirects=false | + """ + endpoint = self._input_string(endpoint) + request = deepcopy(self.request) + request["method"] = "OPTIONS" + if allow_redirects is not None: + request["allowRedirects"] = self._input_boolean(allow_redirects) + if timeout is not None: + request["timeout"] = self._input_timeout(timeout) + validate = self._input_boolean(validate) + if headers: + request["headers"].update(self._input_object(headers)) + return self._request(endpoint, request, validate)["response"] + + @keyword(name=None, tags=("http",)) + def get( + self, + endpoint, + query=None, + timeout=None, + allow_redirects=None, + validate=True, + headers=None, + ): + """*Sends a GET request to the endpoint.* + + The endpoint is joined with the URL given on library init (if any). + If endpoint starts with ``http://`` or ``https://``, it is assumed + an URL outside the tested API (which may affect logging). + + *Options* + + ``query``: Request query parameters as a JSON object or a dictionary. + Alternatively, query parameters can be given as part of endpoint as well. + + ``timeout``: A number of seconds to wait for the response before failing the keyword. + + ``allow_redirects``: If false, do not follow any redirects. + + ``validate``: If false, skips any request and response validations set + by expectation keywords and a spec given on library init. + + ``headers``: Headers as a JSON object to add or override for the request. + + *Examples* + + | `GET` | /users/1 | + | `GET` | /users | timeout=2.5 | + | `GET` | /users?_limit=2 | + | `GET` | /users | _limit=2 | + | `GET` | /users | { "_limit": "2" } | + | `GET` | https://jsonplaceholder.typicode.com/users | headers={ "Authentication": "" } | + """ + endpoint = self._input_string(endpoint) + request = deepcopy(self.request) + request["method"] = "GET" + request["query"] = OrderedDict() + query_in_url = OrderedDict(parse_qsl(urlparse(endpoint).query)) + if query_in_url: + request["query"].update(query_in_url) + endpoint = endpoint.rsplit("?", 1)[0] + if query: + request["query"].update(self._input_object(query)) + if allow_redirects is not None: + request["allowRedirects"] = self._input_boolean(allow_redirects) + if timeout is not None: + request["timeout"] = self._input_timeout(timeout) + validate = self._input_boolean(validate) + if headers: + request["headers"].update(self._input_object(headers)) + return self._request(endpoint, request, validate)["response"] + + @keyword(name=None, tags=("http",)) + def post( + self, + endpoint, + body=None, + timeout=None, + allow_redirects=None, + validate=True, + headers=None, + ): + """*Sends a POST request to the endpoint.* + + The endpoint is joined with the URL given on library init (if any). + If endpoint starts with ``http://`` or ``https://``, it is assumed + an URL outside the tested API (which may affect logging). + + *Options* + + ``body``: Request body parameters as a JSON object, file or a dictionary. + + ``timeout``: A number of seconds to wait for the response before failing the keyword. + + ``allow_redirects``: If false, do not follow any redirects. + + ``validate``: If false, skips any request and response validations set + by expectation keywords and a spec given on library init. + + ``headers``: Headers as a JSON object to add or override for the request. + + *Examples* + + | `POST` | /users | { "id": 11, "name": "Gil Alexander" } | + | `POST` | /users | ${CURDIR}/new_user.json | + """ + endpoint = self._input_string(endpoint) + request = deepcopy(self.request) + request["method"] = "POST" + request["body"] = self.input(body) + if allow_redirects is not None: + request["allowRedirects"] = self._input_boolean(allow_redirects) + if timeout is not None: + request["timeout"] = self._input_timeout(timeout) + validate = self._input_boolean(validate) + if headers: + request["headers"].update(self._input_object(headers)) + return self._request(endpoint, request, validate)["response"] + + @keyword(name=None, tags=("http",)) + def put( + self, + endpoint, + body=None, + timeout=None, + allow_redirects=None, + validate=True, + headers=None, + ): + """*Sends a PUT request to the endpoint.* + + The endpoint is joined with the URL given on library init (if any). + If endpoint starts with ``http://`` or ``https://``, it is assumed + an URL outside the tested API (which may affect logging). + + *Options* + + ``body``: Request body parameters as a JSON object, file or a dictionary. + + ``timeout``: A number of seconds to wait for the response before failing the keyword. + + ``allow_redirects``: If false, do not follow any redirects. + + ``validate``: If false, skips any request and response validations set + by expectation keywords and a spec given on library init. + + ``headers``: Headers as a JSON object to add or override for the request. + + *Examples* + + | `PUT` | /users/2 | { "name": "Julie Langford", "username": "jlangfor" } | + | `PUT` | /users/2 | ${dict} | + """ + endpoint = self._input_string(endpoint) + request = deepcopy(self.request) + request["method"] = "PUT" + request["body"] = self.input(body) + if allow_redirects is not None: + request["allowRedirects"] = self._input_boolean(allow_redirects) + if timeout is not None: + request["timeout"] = self._input_timeout(timeout) + validate = self._input_boolean(validate) + if headers: + request["headers"].update(self._input_object(headers)) + return self._request(endpoint, request, validate)["response"] + + @keyword(name=None, tags=("http",)) + def patch( + self, + endpoint, + body=None, + timeout=None, + allow_redirects=None, + validate=True, + headers=None, + ): + """*Sends a PATCH request to the endpoint.* + + The endpoint is joined with the URL given on library init (if any). + If endpoint starts with ``http://`` or ``https://``, it is assumed + an URL outside the tested API (which may affect logging). + + *Options* + + ``body``: Request body parameters as a JSON object, file or a dictionary. + + ``timeout``: A number of seconds to wait for the response before failing the keyword. + + ``allow_redirects``: If false, do not follow any redirects. + + ``validate``: If false, skips any request and response validations set + by expectation keywords and a spec given on library init. + + ``headers``: Headers as a JSON object to add or override for the request. + + *Examples* + + | `PATCH` | /users/4 | { "name": "Clementine Bauch" } | + | `PATCH` | /users/4 | ${dict} | + """ + endpoint = self._input_string(endpoint) + request = deepcopy(self.request) + request["method"] = "PATCH" + request["body"] = self.input(body) + if allow_redirects is not None: + request["allowRedirects"] = self._input_boolean(allow_redirects) + if timeout is not None: + request["timeout"] = self._input_timeout(timeout) + validate = self._input_boolean(validate) + if headers: + request["headers"].update(self._input_object(headers)) + return self._request(endpoint, request, validate)["response"] + + @keyword(name=None, tags=("http",)) + def delete( + self, + endpoint, + timeout=None, + allow_redirects=None, + validate=True, + headers=None, + ): + """*Sends a DELETE request to the endpoint.* + + The endpoint is joined with the URL given on library init (if any). + If endpoint starts with ``http://`` or ``https://``, it is assumed + an URL outside the tested API (which may affect logging). + + *Options* + + ``timeout``: A number of seconds to wait for the response before failing the keyword. + + ``allow_redirects``: If false, do not follow any redirects. + + ``validate``: If false, skips any request and response validations set + by expectation keywords and a spec given on library init. + + ``headers``: Headers as a JSON object to add or override for the request. + + *Examples* + + | `DELETE` | /users/6 | + | `DELETE` | http://localhost:8273/state | validate=false | + """ + endpoint = self._input_string(endpoint) + request = deepcopy(self.request) + request["method"] = "DELETE" + if allow_redirects is not None: + request["allowRedirects"] = self._input_boolean(allow_redirects) + if timeout is not None: + request["timeout"] = self._input_timeout(timeout) + validate = self._input_boolean(validate) + if headers: + request["headers"].update(self._input_object(headers)) + return self._request(endpoint, request, validate)["response"] + + @keyword(name=None, tags=("assertions",)) + def missing(self, field): + """*Asserts the field does not exist.* + + The field consists of parts separated by spaces, the parts being + object property names or array indices starting from 0, and the root + being the instance created by the last request (see `Output` for it). + + For asserting deeply nested properties or multiple objects at once, + [http://goessner.net/articles/JsonPath|JSONPath] can be used with + [https://github.com/h2non/jsonpath-ng#jsonpath-syntax|supported JSONPath expressions], + the root being the response body. + + *Examples* + + | `GET` | /users/1 | # https://jsonplaceholder.typicode.com/users/1 | + | `Missing` | response body password | + | `Missing` | $.password | + | `Missing` | $..geo.elevation | # response body address geo elevation | + + | `GET` | /users | # https://jsonplaceholder.typicode.com/users | + | `Missing` | response body 0 password | + | `Missing` | $[*].password | + | `Missing` | $[*]..geo.elevation | + """ + try: + matches = self._find_by_field(field, print_found=False) + except AssertionError: + return + for found in matches: + self.log_json( + found["reality"], + "\n\nExpected '%s' to not exist, but it is:" % (field), + ) + raise AssertionError( + "Expected '%s' to not exist, but it does." % (field) + ) + + @keyword(name=None, tags=("assertions",)) + def null(self, field, **validations): + """*Asserts the field as JSON null.* + + The field consists of parts separated by spaces, the parts being + object property names or array indices starting from 0, and the root + being the instance created by the last request (see `Output` for it). + + For asserting deeply nested properties or multiple objects at once, + [http://goessner.net/articles/JsonPath|JSONPath] can be used with + [https://github.com/h2non/jsonpath-ng#jsonpath-syntax|supported JSONPath expressions], + the root being the response body. + + *Validations* + + The JSON Schema validation keywords + [https://json-schema.org/understanding-json-schema/reference/generic.html|common for all types] + can be used. Validations are optional but update the schema of + the property (more accurate) if given. + `Output Schema` can be used for the current schema of the field. + + The keyword will fail if any of the given validations fail. + Given validations can be skipped altogether by adding ``skip=true``. + When skipped, the schema is updated but the validations are not ran. + Skip is intented mainly for debugging the updated schema before aborting. + + *Examples* + + | `PUT` | /users/1 | { "deactivated_at": null } | # https://jsonplaceholder.typicode.com/users/1 | + | `Null` | response body deactivated_at | | + | `Null` | $.deactivated_at | | # JSONPath alternative | + """ + values = [] + for found in self._find_by_field(field): + reality = found["reality"] + schema = {"type": "null"} + skip = self._input_boolean(validations.pop("skip", False)) + if not skip: + self._assert_schema(schema, reality) + values.append(reality) + return values + + @keyword(name=None, tags=("assertions",)) + def boolean(self, field, value=None, **validations): + """*Asserts the field as JSON boolean.* + + The field consists of parts separated by spaces, the parts being + object property names or array indices starting from 0, and the root + being the instance created by the last request (see `Output` for it). + + For asserting deeply nested properties or multiple objects at once, + [http://goessner.net/articles/JsonPath|JSONPath] can be used with + [https://github.com/h2non/jsonpath-ng#jsonpath-syntax|supported JSONPath expressions], the root being the response body. + + *Value* + + If given, the property value is validated in addition to the type. + + *Validations* + + The JSON Schema validation keywords + [https://json-schema.org/understanding-json-schema/reference/generic.html|common for all types] + can be used. Validations are optional but update the schema of + the property (more accurate) if given. + `Output Schema` can be used for the current schema of the field. + + The keyword will fail if any of the given validations fail. + Given validations can be skipped altogether by adding ``skip=true``. + When skipped, the schema is updated but the validations are not ran. + Skip is intented mainly for debugging the updated schema before aborting. + + *Examples* + + | `PUT` | /users/1 | { "verified_email": true } | | | # https://jsonplaceholder.typicode.com/users/1 | + | `Boolean` | response body verified_email | | | | # value is optional | + | `Boolean` | response body verified_email | true | + | `Boolean` | response body verified_email | ${True} | | | # same as above | + | `Boolean` | $.verified_email | true | | | # JSONPath alternative | + | `Boolean` | $.verified_email | true | enum=[1, "1"] | skip=true | # would pass | + """ + values = [] + for found in self._find_by_field(field): + reality = found["reality"] + schema = {"type": "boolean"} + if value is not None: + schema["enum"] = [self._input_boolean(value)] + elif self._should_add_examples(): + schema["examples"] = [reality] + skip = self._input_boolean(validations.pop("skip", False)) + if not skip: + self._assert_schema(schema, reality) + values.append(reality) + return values + + @keyword(name=None, tags=("assertions",)) + def integer(self, field, *enum, **validations): + """*Asserts the field as JSON integer.* + + The field consists of parts separated by spaces, the parts being + object property names or array indices starting from 0, and the root + being the instance created by the last request (see `Output` for it). + + For asserting deeply nested properties or multiple objects at once, + [http://goessner.net/articles/JsonPath|JSONPath] can be used with + [https://github.com/h2non/jsonpath-ng#jsonpath-syntax|supported JSONPath expressions], + the root being the response body. + + *Enum* + + The allowed values for the property as zero or more arguments. + If none given, the value of the property is not asserted. + + *Validations* + + The JSON Schema validation keywords + [https://json-schema.org/understanding-json-schema/reference/numeric.html#integer|for numeric types] + can be used. Validations are optional but update the schema of + the property (more accurate) if given. + `Output Schema` can be used for the current schema of the field. + + The keyword will fail if any of the given validations fail. + Given validations can be skipped altogether by adding ``skip=true``. + When skipped, the schema is updated but the validations are not ran. + Skip is intented mainly for debugging the updated schema before aborting. + + *Examples* + + | `GET` | /users/1 | | # https://jsonplaceholder.typicode.com/users/1 | + | `Integer` | response body id | | # value is optional | + | `Integer` | response body id | 1 | + | `Integer` | response body id | ${1} | # same as above | + | `Integer` | $.id | 1 | # JSONPath alternative | + + | `GET` | /users?_limit=10 | | | | # https://jsonplaceholder.typicode.com/users | + | `Integer` | response body 0 id | 1 | | | + | `Integer` | $[0].id | 1 | | | # same as above | + | `Integer` | $[*].id | | minimum=1 | maximum=10 | + """ + values = [] + for found in self._find_by_field(field): + schema = found["schema"] + reality = found["reality"] + skip = self._input_boolean(validations.pop("skip", False)) + self._set_type_validations("integer", schema, validations) + if enum: + if "enum" not in schema: + schema["enum"] = [] + for value in enum: + value = self._input_integer(value) + if value not in schema["enum"]: + schema["enum"].append(value) + elif self._should_add_examples(): + schema["examples"] = [reality] + if not skip: + self._assert_schema(schema, reality) + values.append(reality) + return values + + @keyword(name=None, tags=("assertions",)) + def number(self, field, *enum, **validations): + """*Asserts the field as JSON number.* + + The field consists of parts separated by spaces, the parts being + object property names or array indices starting from 0, and the root + being the instance created by the last request (see `Output` for it). + + For asserting deeply nested properties or multiple objects at once, + [http://goessner.net/articles/JsonPath|JSONPath] can be used with + [https://github.com/h2non/jsonpath-ng#jsonpath-syntax|supported JSONPath expressions], + the root being the response body. + + *Enum* + + The allowed values for the property as zero or more arguments. + If none given, the value of the property is not asserted. + + *Validations* + + The JSON Schema validation keywords + [https://json-schema.org/understanding-json-schema/reference/numeric.html#number|for numeric types] can be used. Validations are optional but update the schema of + the property (more accurate) if given. + `Output Schema` can be used for the current schema of the field. + + The keyword will fail if any of the given validations fail. + Given validations can be skipped altogether by adding ``skip=true``. + When skipped, the schema is updated but the validations are not ran. + Skip is intented mainly for debugging the updated schema before aborting. + + *Examples* + + | `PUT` | /users/1 | { "allocation": 95.0 } | # https://jsonplaceholder.typicode.com/users/1 | + | `Number` | response body allocation | | # value is optional | + | `Number` | response body allocation | 95.0 | + | `Number` | response body allocation | ${95.0} | # same as above | + | `Number` | $.allocation | 95.0 | # JSONPath alternative | + + | `GET` | /users?_limit=10 | | | | # https://jsonplaceholder.typicode.com/users | + | `Number` | $[0].id | 1 | | | # integers are also numbers | + | `Number` | $[*].id | | minimum=1 | maximum=10 | + """ + values = [] + for found in self._find_by_field(field): + schema = found["schema"] + reality = found["reality"] + skip = self._input_boolean(validations.pop("skip", False)) + self._set_type_validations("number", schema, validations) + if enum: + if "enum" not in schema: + schema["enum"] = [] + for value in enum: + value = self._input_number(value) + if value not in schema["enum"]: + schema["enum"].append(value) + elif self._should_add_examples(): + schema["examples"] = [reality] + if not skip: + self._assert_schema(schema, reality) + values.append(reality) + return values + + @keyword(name=None, tags=("assertions",)) + def string(self, field, *enum, **validations): + """*Asserts the field as JSON string.* + + The field consists of parts separated by spaces, the parts being + object property names or array indices starting from 0, and the root + being the instance created by the last request (see `Output` for it). + + For asserting deeply nested properties or multiple objects at once, + [http://goessner.net/articles/JsonPath|JSONPath] can be used with + [https://github.com/h2non/jsonpath-ng#jsonpath-syntax|supported JSONPath expressions], the root being the response body. + + *Enum* + + The allowed values for the property as zero or more arguments. + If none given, the value of the property is not asserted. + + *Validations* + + The JSON Schema validation keywords + [https://json-schema.org/understanding-json-schema/reference/string.html|for string] + can be used. Validations are optional but update the schema of + the property (more accurate) if given. + `Output Schema` can be used for the current schema of the field. + + The keyword will fail if any of the given validations fail. + Given validations can be skipped altogether by adding ``skip=true``. + When skipped, the schema is updated but the validations are not ran. + Skip is intented mainly for debugging the updated schema before aborting. + + *Examples* + + | `GET` | /users/1 | | | # https://jsonplaceholder.typicode.com/users/1 | + | `String` | response body email | | | # value is optional | + | `String` | response body email | Sincere@april.biz | + | `String` | $.email | Sincere@april.biz | | # JSONPath alternative | + | `String` | $.email | | format=email | + + | `GET` | /users?_limit=10 | | | # https://jsonplaceholder.typicode.com/users | + | `String` | response body 0 email | Sincere@april.biz | + | `String` | $[0].email | Sincere@april.biz | | # same as above | + | `String` | $[*].email | | format=email | + """ + values = [] + for found in self._find_by_field(field): + schema = found["schema"] + reality = found["reality"] + skip = self._input_boolean(validations.pop("skip", False)) + self._set_type_validations("string", schema, validations) + if enum: + if "enum" not in schema: + schema["enum"] = [] + for value in enum: + value = self._input_string(value) + if value not in schema["enum"]: + schema["enum"].append(value) + elif self._should_add_examples(): + schema["examples"] = [reality] + if not skip: + self._assert_schema(schema, reality) + values.append(reality) + return values + + @keyword(name=None, tags=("assertions",)) + def object(self, field, *enum, **validations): + """*Asserts the field as JSON object.* + + The field consists of parts separated by spaces, the parts being + object property names or array indices starting from 0, and the root + being the instance created by the last request (see `Output` for it). + + For asserting deeply nested properties or multiple objects at once, + [http://goessner.net/articles/JsonPath|JSONPath] can be used with + [https://github.com/h2non/jsonpath-ng#jsonpath-syntax|supported JSONPath expressions], the root being the response body. + + *Enum* + + The allowed values for the property as zero or more arguments. + If none given, the value of the property is not asserted. + + *Validations* + + The JSON Schema validation keywords + [https://json-schema.org/understanding-json-schema/reference/object.html|for object] + can be used. Validations are optional but update the schema of + the property (more accurate) if given. + `Output Schema` can be used for the current schema of the field. + + The keyword will fail if any of the given validations fail. + Given validations can be skipped altogether by adding ``skip=true``. + When skipped, the schema is updated but the validations are not ran. + Skip is intented mainly for debugging the updated schema before aborting. + + *Examples* + + | `GET` | /users/1 | | # https://jsonplaceholder.typicode.com/users/1 | + | `Object` | response body | | + | `Object` | response body | required=["id", "name"] | # can have other properties | + + | `GET` | /users/1 | | # https://jsonplaceholder.typicode.com/users/1 | + | `Object` | $.address.geo | required=["lat", "lng"] | + | `Object` | $..geo | additionalProperties=false | # cannot have other properties | + """ + values = [] + for found in self._find_by_field(field): + schema = found["schema"] + reality = found["reality"] + skip = self._input_boolean(validations.pop("skip", False)) + self._set_type_validations("object", schema, validations) + if enum: + if "enum" not in schema: + schema["enum"] = [] + for value in enum: + value = self._input_object(value) + if value not in schema["enum"]: + schema["enum"].append(value) + elif self._should_add_examples(): + schema["examples"] = [reality] + if not skip: + self._assert_schema(schema, reality) + values.append(reality) + return values + + @keyword(name=None, tags=("assertions",)) + def array(self, field, *enum, **validations): + """*Asserts the field as JSON array.* + + The field consists of parts separated by spaces, the parts being + object property names or array indices starting from 0, and the root + being the instance created by the last request (see `Output` for it). + + For asserting deeply nested properties or multiple objects at once, + [http://goessner.net/articles/JsonPath|JSONPath] can be used with + [https://github.com/h2non/jsonpath-ng#jsonpath-syntax|supported JSONPath expressions], + the root being the response body. + + *Enum* + + The allowed values for the property as zero or more arguments. + If none given, the value of the property is not asserted. + + *Validations* + + The JSON Schema validation keywords + [https://json-schema.org/understanding-json-schema/reference/array.html|for array] + can be used. Validations are optional but update the schema of + the property (more accurate) if given. + `Output Schema` can be used for the current schema of the field. + + The keyword will fail if any of the given validations fail. + Given validations can be skipped altogether by adding ``skip=true``. + When skipped, the schema is updated but the validations are not ran. + Skip is intented mainly for debugging the updated schema before aborting. + + *Examples* + | `GET` | /users?_limit=10 | | | | # https://jsonplaceholder.typicode.com/users | + | `Array` | response body | | | + | `Array` | $ | | | | # same as above | + | `Array` | $ | minItems=1 | maxItems=10 | uniqueItems=true | + """ + values = [] + for found in self._find_by_field(field): + schema = found["schema"] + reality = found["reality"] + skip = self._input_boolean(validations.pop("skip", False)) + self._set_type_validations("array", schema, validations) + if enum: + if "enum" not in schema: + schema["enum"] = [] + for value in enum: + value = self._input_array(value) + if value not in schema["enum"]: + schema["enum"].append(value) + elif self._should_add_examples(): + schema["examples"] = [reality] + if not skip: + self._assert_schema(schema, reality) + values.append(reality) + return values + + @keyword(name=None, tags=("I/O",)) + def input(self, what): + """*Converts the input to JSON and returns it.* + + Any of the following is accepted: + + - The path to JSON file + - Any scalar that can be interpreted as JSON + - A dictionary or a list + + *Examples* + + | ${payload} | `Input` | ${CURDIR}/payload.json | + + | ${object} | `Input` | { "name": "Julie Langford", "username": "jlangfor" } | + | ${object} | `Input` | ${dict} | + + | ${array} | `Input` | ["name", "username"] | + | ${array} | `Input` | ${list} | + + | ${boolean} | `Input` | true | + | ${boolean} | `Input` | ${True} | + + | ${number} | `Input` | 2.0 | + | ${number} | `Input` | ${2.0} | + + | ${string} | `Input` | Quotes are optional for strings | + """ + if what is None: + return None + if not isinstance(what, STRING_TYPES): + return self._input_json_from_non_string(what) + if path.isfile(what): + return self._input_json_from_file(what) + try: + return self._input_json_as_string(what) + except ValueError: + return self._input_string(what) + + @keyword(name=None, tags=("I/O",)) + def output_schema( + self, what="", file_path=None, append=False, sort_keys=False + ): + """*Outputs JSON Schema to terminal or a file.* + + By default, the schema is output for the last request and response. + + The output can be limited further by: + + - The property of the last instance, e.g. ``request`` or ``response`` + - Any nested property that exists, similarly as for assertion keywords + + Also variables and values that can be converted to JSON are accepted, + in which case the schema is generated for those instead. + + *Options* + + ``file_path``: The JSON Schema is written to a file instead of terminal. + The file is created if it does not exist. + + ``append``: If true, the JSON Schema is appended to the given file + instead of truncating it first. + + ``sort_keys``: If true, the JSON Schema is sorted alphabetically by + property names before it is output. + + *Examples* + + | `Output Schema` | response | ${CURDIR}/response_schema.json | # Write a file to use with `Expect Response` | + | `Output Schema` | response body | ${CURDIR}/response_body_schema.json | # Write a file to use with `Expect Response Body` | + + | `Output Schema` | $.email | # only the schema for one response body property | + | `Output Schema` | $..geo | # only the schema for the nested response body property | + """ + if isinstance(what, (STRING_TYPES)): + if what == "": + try: + json = self._last_instance_or_error()["schema"] + except IndexError: + raise RuntimeError(no_instances_error) + elif what.startswith(("request", "response", "$")): + self._last_instance_or_error() + matches = self._find_by_field(what) + if len(matches) > 1: + json = [found["schema"] for found in matches] + else: + json = matches[0]["schema"] + else: + try: + json = self._new_schema(self._input_json_as_string(what)) + except ValueError: + json = self._new_schema(self._input_string(what)) + else: + json = self._new_schema(self._input_json_from_non_string(what)) + sort_keys = self._input_boolean(sort_keys) + if not file_path: + self.log_json(json, sort_keys=sort_keys) + else: + content = dumps( + json, + ensure_ascii=False, + indent=4, + separators=(",", ": "), + sort_keys=sort_keys, + ) + write_mode = "a" if self._input_boolean(append) else "w" + try: + with open( + path.join(getcwd(), file_path), write_mode, encoding="utf-8" + ) as file: + if IS_PYTHON_2: + content = unicode(content) + file.write(content) + except IOError as e: + raise RuntimeError( + "Error outputting to file '%s':\n%s" % (file_path, e) + ) + return json + + @keyword(name=None, tags=("I/O",)) + def output(self, what="", file_path=None, append=False, sort_keys=False): + """*Outputs JSON to terminal or a file.* + + By default, the last request and response are output to terminal. + + The output can be limited further by: + + - The property of the last instance, e.g. ``request`` or ``response`` + - Any nested property that exists, similarly as for assertion keywords + + Also variables and values that can be converted to JSON are accepted, + in which case those are output as JSON instead. + + *Options* + + ``file_path``: The JSON is written to a file instead of terminal. + The file is created if it does not exist. + + ``append``: If true, the JSON is appended to the given file + instead of truncating it first. + + ``sort_keys``: If true, the JSON is sorted alphabetically by + property names before it is output. + + *Examples* + + | `Output` | response | # only the response is output | + | `Output` | response body | # only the response body is output | + | `Output` | $.email | # only the response body property is output | + | `Output` | $..geo | # only the nested response body property is output | + + | `Output` | request | # only the request is output | + | `Output` | request headers | # only the request headers are output | + | `Output` | request headers Authentication | # only this header is output | + + | `Output` | response body | ${CURDIR}/response_body.json | | # write the response body to a file | + | `Output` | response seconds | ${CURDIR}/response_delays.log | append=true | # keep track of response delays in a file | + """ + if isinstance(what, (STRING_TYPES)): + if what == "": + try: + json = deepcopy(self._last_instance_or_error()) + json.pop("schema") + json.pop("spec") + except IndexError: + raise RuntimeError(no_instances_error) + elif what.startswith("schema"): + logger.warn( + "Using `Output` for schema is deprecated. " + + "Using `Output Schema` to handle schema paths better." + ) + what = what.lstrip("schema").lstrip() + return self.output_schema(what, file_path, append, sort_keys) + elif what.startswith(("request", "response", "$")): + self._last_instance_or_error() + matches = self._find_by_field(what, return_schema=False) + if len(matches) > 1: + json = [found["reality"] for found in matches] + else: + json = matches[0]["reality"] + else: + try: + json = self._input_json_as_string(what) + except ValueError: + json = self._input_string(what) + else: + json = self._input_json_from_non_string(what) + sort_keys = self._input_boolean(sort_keys) + if not file_path: + self.log_json(json, sort_keys=sort_keys) + else: + content = dumps( + json, + ensure_ascii=False, + indent=4, + separators=(",", ": "), + sort_keys=sort_keys, + ) + write_mode = "a" if self._input_boolean(append) else "w" + try: + with open( + path.join(getcwd(), file_path), write_mode, encoding="utf-8" + ) as file: + if IS_PYTHON_2: + content = unicode(content) + file.write(content) + except IOError as e: + raise RuntimeError( + "Error outputting to file '%s':\n%s" % (file_path, e) + ) + return json + + @keyword(name=None, tags=("I/O",)) + def rest_instances(self, file_path=None, sort_keys=False): + """*Writes the instances as JSON to a file.* + + The instances are written to file as a JSON array of JSON objects, + each object representing a single instance, and having three properties: + + - the request + - the response + - the schema for both, which have been updated according to the tests + + The file is created if it does not exist, otherwise it is truncated. + + *Options* + + ``sort_keys``: If true, the instances are sorted alphabetically by + property names. + + *Examples* + + | `Rest Instances` | ${CURDIR}/log.json | + """ + if not file_path: + outputdir_path = BuiltIn().get_variable_value("${OUTPUTDIR}") + if self.request["netloc"]: + file_path = ( + path.join(outputdir_path, self.request["netloc"]) + ".json" + ) + else: + file_path = path.join(outputdir_path, "instances") + ".json" + sort_keys = self._input_boolean(sort_keys) + content = dumps( + self.instances, + ensure_ascii=False, + indent=4, + separators=(",", ": "), + sort_keys=sort_keys, + ) + try: + with open(file_path, "w", encoding="utf-8") as file: + if IS_PYTHON_2: + content = unicode(content) + file.write(content) + except IOError as e: + raise RuntimeError( + "Error exporting instances " + + "to file '%s':\n%s" % (file_path, e) + ) + return self.instances + + ### Internal methods + + def _request(self, endpoint, request, validate=True): + if not endpoint.startswith(("http://", "https://")): + base_url = self.request["scheme"] + "://" + self.request["netloc"] + if not endpoint.startswith("/"): + endpoint = "/" + endpoint + endpoint = urljoin(base_url, self.request["path"]) + endpoint + request["url"] = endpoint + url_parts = urlparse(request["url"]) + request["scheme"] = url_parts.scheme + request["netloc"] = url_parts.netloc + request["path"] = url_parts.path + try: + response = client( + request["method"], + request["url"], + params=request["query"], + json=request["body"], + headers=request["headers"], + proxies=request["proxies"], + cert=request["cert"], + timeout=tuple(request["timeout"]), + allow_redirects=request["allowRedirects"], + verify=request["sslVerify"], + ) + except SSLError as e: + raise AssertionError( + "%s to %s SSL certificate verify failed:\n%s" + % (request["method"], request["url"], e) + ) + except Timeout as e: + raise AssertionError( + "%s to %s timed out:\n%s" + % (request["method"], request["url"], e) + ) + utc_datetime = datetime.now(tz=utc) + request["timestamp"] = {} + request["timestamp"]["utc"] = utc_datetime.isoformat() + try: + request["timestamp"]["local"] = utc_datetime.astimezone( + get_localzone() + ).isoformat() + except UnknownTimeZoneError as e: + logger.info("Cannot infer local timestamp! tzlocal:%s" % str(e)) + if validate and self.spec: + self._assert_spec(self.spec, response) + instance = self._instantiate(request, response, validate) + self.instances.append(instance) + return instance + + def _instantiate(self, request, response, validate_schema=True): + try: + response_body = response.json() + except ValueError: + response_body = response.text + if response_body: + logger.warn( + "Response body content is not JSON. " + + "Content-Type is: %s" % response.headers["Content-Type"] + ) + response = { + "seconds": response.elapsed.microseconds / 1000 / 1000, + "status": response.status_code, + "body": response_body, + "headers": dict(response.headers), + } + schema = deepcopy(self.schema) + schema["title"] = "%s %s" % (request["method"], request["url"]) + try: + schema["description"] = "%s: %s" % ( + BuiltIn().get_variable_value("${SUITE NAME}"), + BuiltIn().get_variable_value("${TEST NAME}"), + ) + except RobotNotRunningError: + schema["description"] = "" + request_properties = schema["properties"]["request"]["properties"] + response_properties = schema["properties"]["response"]["properties"] + if validate_schema: + if request_properties: + self._validate_schema(request_properties, request) + if response_properties: + self._validate_schema(response_properties, response) + request_properties["body"] = self._new_schema(request["body"]) + request_properties["query"] = self._new_schema(request["query"]) + response_properties["body"] = self._new_schema(response["body"]) + if "default" in schema and schema["default"]: + self._add_defaults_to_schema(schema, response) + return { + "request": request, + "response": response, + "schema": schema, + "spec": self.spec, + } + + def _assert_spec(self, spec, response): + request = response.request + try: + validate_api_call(spec, raw_request=request, raw_response=response) + except ValueError as e: + raise AssertionError(e) + + def _validate_schema(self, schema, json_dict): + for field in schema: + self._assert_schema(schema[field], json_dict[field]) + + def _assert_schema(self, schema, reality): + try: + validate(reality, schema, format_checker=FormatChecker()) + except SchemaError as e: + raise RuntimeError(e) + except ValidationError as e: + raise AssertionError(e) + + def _new_schema(self, value): + builder = SchemaBuilder(schema_uri=False) + builder.add_object(value) + return builder.to_schema() + + def _add_defaults_to_schema(self, schema, response): + body = response["body"] + schema = schema["properties"]["response"]["properties"]["body"] + if isinstance(body, (dict)) and "properties" in schema: + self._add_property_defaults(body, schema["properties"]) + + def _add_property_defaults(self, body, schema): + for key in body: + if "properties" in schema[key]: + self._add_property_defaults( + body[key], schema[key]["properties"] + ) + else: + schema[key]["default"] = body[key] + + def _find_by_field(self, field, return_schema=True, print_found=True): + last_instance = self._last_instance_or_error() + schema = None + paths = [] + if field.startswith("$"): + value = last_instance["response"]["body"] + if return_schema: + schema = last_instance["schema"]["properties"]["response"] + schema = schema["properties"]["body"] + if field == "$": + return [ + { + "path": ["response", "body"], + "reality": value, + "schema": schema, + } + ] + try: + query = parse_jsonpath(field) + except Exception as e: + raise RuntimeError( + "Invalid JSONPath query '%s': %s" % (field, e) + ) + matches = [str(match.full_path) for match in query.find(value)] + if not matches: + raise AssertionError( + "JSONPath query '%s' " % (field) + "did not match anything." + ) + for match in matches: + path = match.replace("[", "").replace("]", "").split(".") + paths.append(path) + else: + value = last_instance + if return_schema: + schema = last_instance["schema"]["properties"] + path = field.split() + paths.append(path) + return [ + self._find_by_path(field, path, value, schema, print_found) + for path in paths + ] + + def _last_instance_or_error(self): + try: + return self.instances[-1] + except IndexError: + raise RuntimeError( + "No instances: No requests made, " + + "and no previous instances loaded in the library import." + ) + + def _find_by_path(self, field, path, value, schema=None, print_found=True): + for key in path: + try: + value = self._value_by_key(value, key) + except (KeyError, TypeError): + if print_found: + self.log_json( + value, "\n\nProperty '%s' does not exist in:" % (key) + ) + raise AssertionError( + "\nExpected property '%s' was not found." % (field) + ) + except IndexError: + if print_found: + self.log_json( + value, "\n\nIndex '%s' does not exist in:" % (key) + ) + raise AssertionError( + "\nExpected index '%s' did not exist." % (field) + ) + if schema: + schema = self._schema_by_key(schema, key, value) + found = {"path": path, "reality": value, "schema": schema} + return found + + def _value_by_key(self, json, key): + try: + return json[int(key)] + except ValueError: + return json[key] + + def _schema_by_key(self, schema, key, value): + if "properties" in schema: + schema = schema["properties"] + elif "items" in schema: + if isinstance(schema["items"], (dict)): + schema["items"] = [schema["items"]] + new_schema = self._new_schema(value) + try: + return schema["items"][schema["items"].index(new_schema)] + except ValueError: + schema["items"].append(new_schema) + return schema["items"][-1] + if key not in schema: + schema[key] = self._new_schema(value) + return schema[key] + + def _should_add_examples(self): + return "examples" in self.schema and isinstance( + self.schema["examples"], (list) + ) + + def _set_type_validations(self, json_type, schema, validations): + if validations: + if "draft-04" in self.schema["$schema"]: + schema_version = "draft-04" + elif "draft-06" in self.schema["$schema"]: + schema_version = "draft-06" + else: + schema_version = "draft-07" + kws = list(SCHEMA_KEYWORDS["common"][schema_version]) + kws.extend(SCHEMA_KEYWORDS[json_type][schema_version]) + for validation in validations: + if validation not in kws: + raise RuntimeError( + "Unknown JSON Schema (%s)" % (schema_version) + + " validation keyword " + + "for %s:\n%s" % (json_type, validation) + ) + schema[validation] = self.input(validations[validation]) + schema.update({"type": json_type}) diff --git a/robot/lib/python3.8/site-packages/REST/schema_keywords.py b/robot/lib/python3.8/site-packages/REST/schema_keywords.py new file mode 100644 index 0000000000000000000000000000000000000000..a207fc62b522ed710649b5b4cb6d8e0f134b8e6f --- /dev/null +++ b/robot/lib/python3.8/site-packages/REST/schema_keywords.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018- Anssi Syrjäsalo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +GENSON_GENERATED_KEYWORDS = ( + "$schema", + "anyOf", + "items", + "patternProperties", + "properties", + "required", + "type", +) + +SCHEMA_KEYWORDS = { + "common": { + "draft-04": ( + "enum", + "type", + "allOf", + "anyOf", + "oneOf", + "not", + "definitions", + ), + "draft-06": ( + "enum", + "type", + "allOf", + "anyOf", + "oneOf", + "not", + "definitions", + "const", + "contains", + "propertyNames", + ), + "draft-07": ( + "enum", + "type", + "allOf", + "anyOf", + "oneOf", + "not", + "definitions", + "const", + "contains", + "propertyNames", + ), + }, + "integer": { + "draft-04": ( + "multipleOf", + "maximum", + "exclusiveMaximum", + "minimum", + "exclusiveMinimum", + ), + "draft-06": ( + "multipleOf", + "maximum", + "exclusiveMaximum", + "minimum", + "exclusiveMinimum", + ), + "draft-07": ( + "multipleOf", + "maximum", + "exclusiveMaximum", + "minimum", + "exclusiveMinimum", + ), + }, + "number": { + "draft-04": ( + "multipleOf", + "maximum", + "exclusiveMaximum", + "minimum", + "exclusiveMinimum", + ), + "draft-06": ( + "multipleOf", + "maximum", + "exclusiveMaximum", + "minimum", + "exclusiveMinimum", + ), + "draft-07": ( + "multipleOf", + "maximum", + "exclusiveMaximum", + "minimum", + "exclusiveMinimum", + ), + }, + "string": { + "draft-04": ("format", "maxLength", "minLength", "pattern"), + "draft-06": ("format", "maxLength", "minLength", "pattern"), + "draft-07": ("format", "maxLength", "minLength", "pattern"), + }, + "object": { + "draft-04": ( + "maxProperties", + "minProperties", + "required", + "additionalProperties", + "properties", + "patternProperties", + "dependencies", + ), + "draft-06": ( + "maxProperties", + "minProperties", + "required", + "additionalProperties", + "properties", + "patternProperties", + "dependencies", + ), + "draft-07": ( + "maxProperties", + "minProperties", + "required", + "additionalProperties", + "properties", + "patternProperties", + "dependencies", + ), + }, + "array": { + "draft-04": ( + "additionalItems", + "items", + "maxItems", + "minItems", + "uniqueItems", + ), + "draft-06": ( + "additionalItems", + "items", + "maxItems", + "minItems", + "uniqueItems", + ), + "draft-07": ( + "additionalItems", + "items", + "maxItems", + "minItems", + "uniqueItems", + ), + }, +} diff --git a/robot/lib/python3.8/site-packages/REST/version.py b/robot/lib/python3.8/site-packages/REST/version.py new file mode 100644 index 0000000000000000000000000000000000000000..bb3bacfceb4e05ef052f082ce951d5f01648866b --- /dev/null +++ b/robot/lib/python3.8/site-packages/REST/version.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018- Anssi Syrjäsalo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__version__ = "1.0.2" diff --git a/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/METADATA b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..44d98ce394ac758bb6b92c344b852d7e0700bf98 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/METADATA @@ -0,0 +1,388 @@ +Metadata-Version: 2.1 +Name: RESTinstance +Version: 1.0.2 +Summary: Robot Framework library for RESTful JSON APIs +Home-page: https://github.com/asyrjasalo/RESTinstance +Author: Anssi Syrjäsalo +Author-email: anssi.syrjasalo@gmail.com +License: Apache License 2.0 +Download-URL: https://pypi.python.org/pypi/RESTinstance +Keywords: robotframework library http json api +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Framework :: Robot Framework +Classifier: Framework :: Robot Framework :: Library +Classifier: Topic :: Internet +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Software Development :: Testing +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Description-Content-Type: text/x-rst +Requires-Dist: robotframework (>=3.1.0) +Requires-Dist: docutils +Requires-Dist: flex +Requires-Dist: GenSON +Requires-Dist: jsonpath-ng +Requires-Dist: pygments +Requires-Dist: requests +Requires-Dist: jsonschema +Requires-Dist: rfc3987 +Requires-Dist: strict-rfc3339 +Requires-Dist: pytz +Requires-Dist: tzlocal + +RESTinstance +============ + +`Robot Framework `__ library for RESTful JSON APIs + +.. image:: https://circleci.com/gh/asyrjasalo/RESTinstance.svg?style=svg + :target: https://circleci.com/gh/asyrjasalo/RESTinstance + + + +Advantages +---------- + +1. **RESTinstance relies on Robot Framework's language-agnostic, + clean and minimal syntax, for API tests.** It is neither tied to any + particular programming language nor development framework. + Using RESTinstance requires little, if any, programming knowledge. + It builts on long-term technologies with well established communities, + such as HTTP, JSON (Schema), Swagger/OpenAPI and Robot Framework. + +2. **It validates JSON using JSON Schema, guiding you to write API tests + to base on properties** rather than on specific values (e.g. "email + must be valid" vs "email is foo\@bar.com"). This approach reduces test + maintenance when the values responded by the API are prone to change. + Although values are not required, you can still test them whenever they + make sense (e.g. GET response body from one endpoint, then POST some + of its values to another endpoint and verify the results). + +3. **It generates JSON Schema for requests and responses automatically, + and the schema gets more accurate by your tests.** + Output the schema to a file and reuse it as expectations to test the other + methods, as most of them respond similarly with only minor differences. + Or extend the schema further to a full Swagger spec (version 2.0, + OpenAPI 3.0 also planned), which RESTinstance can test requests and + responses against. All this leads to reusability, getting great test + coverage with minimum number of keystrokes and very clean tests. + + + +Installation +------------ + +Pick the one that suits your environment best. + +As a Python package +~~~~~~~~~~~~~~~~~~~ +On 3.6, 3.7 and 2.7, you can install and upgrade `from PyPi `__: + +:: + + pip install --upgrade RESTinstance + +This also installs `Robot Framework `__ if you do not have it already. + +As a Docker image +~~~~~~~~~~~~~~~~~ + +`RESTinstance Docker image `__ +contains Python 3.6.9 and `the latest Robot Framework `__: + +:: + + docker pull asyrjasalo/restinstance + + + +Usage +----- + +There is a `step-by-step tutorial `__ +in the making, best accompanied with `keyword documentation `__. + +Quick start +~~~~~~~~~~~ + +1. Create two new (empty) directories ``tests`` and ``results``. + +2. Create a new file ``atest/YOURNAME.robot`` with content: + +.. code:: robotframework + + *** Settings *** + Library REST https://jsonplaceholder.typicode.com + Documentation Test data can be read from variables and files. + ... Both JSON and Python type systems are supported for inputs. + ... Every request creates a so-called instance. Can be `Output`. + ... Most keywords are effective only for the last instance. + ... Initial schemas are autogenerated for request and response. + ... You can make them more detailed by using assertion keywords. + ... The assertion keywords correspond to the JSON types. + ... They take in either path to the property or a JSONPath query. + ... Using (enum) values in tests optional. Only type is required. + ... All the JSON Schema validation keywords are also supported. + ... Thus, there is no need to write any own validation logic. + ... Not a long path from schemas to full Swagger/OpenAPI specs. + ... The persistence of the created instances is the test suite. + ... Use keyword `Rest instances` to output the created instances. + + + *** Variables *** + ${json} { "id": 11, "name": "Gil Alexander" } + &{dict} name=Julie Langford + + + *** Test Cases *** + GET an existing user, notice how the schema gets more accurate + GET /users/1 # this creates a new instance + Output schema response body + Object response body # values are fully optional + Integer response body id 1 + String response body name Leanne Graham + [Teardown] Output schema # note the updated response schema + + GET existing users, use JSONPath for very short but powerful queries + GET /users?_limit=5 # further assertions are to this + Array response body + Integer $[0].id 1 # first id is 1 + String $[0]..lat -37.3159 # any matching child + Integer $..id maximum=5 # multiple matches + [Teardown] Output $[*].email # outputs all emails as an array + + POST with valid params to create a new user, can be output to a file + POST /users ${json} + Integer response status 201 + [Teardown] Output response body ${OUTPUTDIR}/new_user.demo.json + + PUT with valid params to update the existing user, values matter here + PUT /users/2 { "isCoding": true } + Boolean response body isCoding true + PUT /users/2 { "sleep": null } + Null response body sleep + PUT /users/2 { "pockets": "", "money": 0.02 } + String response body pockets ${EMPTY} + Number response body money 0.02 + Missing response body moving # fails if property moving exists + + PATCH with valid params, reusing response properties as a new payload + &{res}= GET /users/3 + String $.name Clementine Bauch + PATCH /users/4 { "name": "${res.body['name']}" } + String $.name Clementine Bauch + PATCH /users/5 ${dict} + String $.name ${dict.name} + + DELETE the existing successfully, save the history of all requests + DELETE /users/6 # status can be any of the below + Integer response status 200 202 204 + Rest instances ${OUTPUTDIR}/all.demo.json # all the instances so far + + +3. Chose Python installation? Let's go (not that language): + +:: + + robot --outputdir results atest/ + +If you chose the Docker method instead (recall the story about red and blue pill here, if you want), this is quaranteed to work in most environments: + +:: + + docker run --rm -ti --env HOST_UID=$(id -u) --env HOST_GID=$(id -g) \ + --env HTTP_PROXY --env HTTPS_PROXY --network host \ + --volume "$PWD/atest":/home/robot/atest \ + --volume "$PWD/results":/home/robot/results \ + asyrjasalo/restinstance atest/ + +Tip: If you prefer installing from source, ``pip install --editable .`` +and verify the installation with ``robot README.rst`` + + + +Contributing +------------ + +Bug reports and feature requests are tracked in +`GitHub `__. + +We do respect pull request(er)s. Please mention if you do not want to be +listed below as contributors. + +A `CircleCI `__ job is +created automatically for your GitHub pull requests as well. + + +Local development +~~~~~~~~~~~~~~~~~ +On Linux distros and on OS X, may ``make`` rules ease repetitive workflows: + +:: + + $ make help + all_dev (DEFAULT / make): test, install_e, atest + all_github All branches/PRs: test, build, install, atest + all_prepypi Pre to TestPyPI: build, publish_pre, install_pre, atest + all_pypi Final to PyPI: build, publish_prod, install_prod, atest + atest Run Robot atests for the currently installed package + black Reformat ("blacken") all Python source code in-place + build Build source and wheel dists, recreates .venv/release + clean Pip uninstall, rm .venv/s, build, dist, eggs, .caches + docs Regenerate (library) documentation in this source tree + flake8 Run flake8 for detecting flaws via static code analysis + install (Re)install the package from this source tree + install_e Install the package as --editable from this source tree + install_pre (Re)install the latest test.pypi.org (pre-)release + install_prod Install/upgrade to the latest final release in PyPI + prospector Runs static analysis using dodgy, mypy, pyroma and vulture + publish_pre Publish dists to test.pypi.org - for pre, e.g. aX, bX, rcX + publish_prod Publish dists to live PyPI - for final only, e.g. 1.0.1 + pur Update requirements-dev's deps that have versions defined + retest Run only failed unit tests if any, otherwise all + test Run unit tests, upgrades .venv/dev with requirements(-dev) + testenv Start new testenv in docker if available, otherwise local + testenv_rm Stop and remove the running docker testenv if any + uninstall Uninstall the Python package, regardless of its origin + + + +Running ``make`` runs rules ``test``, ``install_e`` and ``atest`` at once, +creates and uses virtualenv ``.venv/dev/`` to ensure that no +(user or system level) dependencies interfere with the process. + +If ``make`` is not available, you can setup for development with: + +:: + + python3 -m venv .venv/dev + source .venv/dev/bin/activate + pip install -r requirements-dev.txt + pip install --editable . + +To recreate the keyword documentation from source (equals to ``make docs``): + +:: + + python3 -m robot.libdoc src/REST docs/index.html + + +Acceptance tests +~~~~~~~~~~~~~~~~ + +The ``testapi/`` is built on `mountebank `__. +You can monitor requests and responses at +`localhost:2525 `__ + +To start the testenv and ran ``robot`` for acceptance tests: + +:: + + make atest + +If you have Docker available, then testenv is ran in Docker container which is +recreated each time the above make rule is ran. + +If Docker is not available, then testenv is ran using local ``mb`` which is +installed and started as following (ran by the make rule, here for reference): + +:: + + npx mountebank --localOnly --allowInjection --configfile testapi/apis.ejs + +The tests are ran as following (ran by the make rule, here for reference): + +:: + + python3 -m robot --outputdir results atest/ + +To run the acceptance tests from a dedicated Docker container, built and ran +outside the the test API, and limit only to specific suite(s): + +:: + + RUN_ARGS="--rm --network=host --env HTTP_PROXY --env HTTPS_PROXY \ + -v $PWD/atest:/home/robot/atest \ + -v $PWD/results:/home/robot/results" \ + ./docker/build_run_docker atest/output.robot + +Host directories ``atest/`` and ``results/`` are accessed inside the container +via the respective Docker volumes. Same arguments are accepted as for ``robot``. + +Host network is used to minimize divergence between different host OSes. +Passing the proxy environment variables may not be required in your environment, +but there should be no downside either. On OS X ``--network=host`` is required. + + +Docker releases +~~~~~~~~~~~~~~~ + +`The Docker image `__ +is built by ``./docker/build_run_docker`` which uses ``docker/Dockerfile``. + +Then, to tag this built image with two git tags, the timestamp and "latest", +and push it to a Docker image registry: + +:: + + REGISTRY_USERNAME=yourname \ + REGISTRY_URL=https://private.registry.com/ \ + ./docker/tag_and_push_docker + +For `Docker Hub `__, just organisation/username will do: + +:: + + REGISTRY_USERNAME=yourname ./docker/tag_and_push_docker + + + +Credits +------- + +RESTinstance is under `Apache License 2.0 `__ +and was originally written by `Anssi Syrjäsalo `__. + +It was first presented at the first `RoboCon `__, 2018. + + +Contributors: + +- `jjwong `__ + for helping with keyword documentation and examples (also check + `RESTinstance_starter_project `__) + +- `Przemysław "sqilz" Hendel `__ + for using and testing RESTinstance in early phase (also check + `RESTinstance-wrapper `__) + +- `Vinh "vinhntb" Nguyen `__, `#52 `__. + +- `Stavros "stdedos" Ntentos `__, `#75 `__. + +We use following Python excellence under the hood: + +- `Flex `__, by Piper Merriam, + for Swagger 2.0 validation +- `GenSON `__, by Jon + "wolverdude" Wolverton, for JSON Schema generator +- `jsonpath-ng `__, + by Tomas Aparicio and Kenneth Knowles, for handling JSONPath queries +- `jsonschema `__, by Julian + Berman, for JSON Schema validator +- `pygments `__, by Georg Brandl et al., + for JSON syntax coloring, in terminal `Output` +- `requests `__, by Kenneth + Reitz et al., for making HTTP requests + +See `requirements.txt `__ for all the direct run time dependencies. + +REST your mind, OSS got your back. + + diff --git a/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/RECORD b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..1ec5a63f43b473875bbf137d91bcb13123323791 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/RECORD @@ -0,0 +1,18 @@ +../../../bin/robot,sha256=cEKA0G1Oenc3VtBTgp5EYyvUHWCnWZpsIuCXQuxjsBQ,258 +REST/__init__.py,sha256=cF8pGY-xx1gVMT0lU-IpDcuqJZB-Zd-ri0-E9REQGnU,14624 +REST/__pycache__/__init__.cpython-38.pyc,, +REST/__pycache__/compat.cpython-38.pyc,, +REST/__pycache__/keywords.cpython-38.pyc,, +REST/__pycache__/schema_keywords.cpython-38.pyc,, +REST/__pycache__/version.cpython-38.pyc,, +REST/compat.py,sha256=Et5Yuxv3vCeKlP0mHO3iuPFvYxcv0TOho_gNkp3QpjE,748 +REST/keywords.py,sha256=nUyo94LGyjZP6-QOEVgqePBQAnRUFEDVffaKYWM7gNs,62613 +REST/schema_keywords.py,sha256=7K1lz0_PVMYhxS6c4z60hTnL29XwisvblTU7hiC-n4o,4032 +REST/version.py,sha256=I5i9DZ0K-aOlbwJRRmjP3WOQuwYhZqiuvvx0j7iOvbk,640 +RESTinstance-1.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +RESTinstance-1.0.2.dist-info/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +RESTinstance-1.0.2.dist-info/METADATA,sha256=a208NFv66M30TkmWz-6xrG6zjCj8-hJOiFNyHxp3b50,15475 +RESTinstance-1.0.2.dist-info/RECORD,, +RESTinstance-1.0.2.dist-info/WHEEL,sha256=p46_5Uhzqz6AzeSosiOnxK-zmFja1i22CrQCjmYe8ec,92 +RESTinstance-1.0.2.dist-info/entry_points.txt,sha256=HquaGE1PnTY22WXw4_SiKsI2EKV5I4u0yPAWc91TgBM,45 +RESTinstance-1.0.2.dist-info/top_level.txt,sha256=jupydh5PyL7lBy_63EcRjNpAEz8WbUCMciVIyzFLjmc,5 diff --git a/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/WHEEL b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..3b5c4038dd7bd845e6b0aecad69991381b2b1331 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.33.6) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/entry_points.txt b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..eee9a445a13ae1da19202c9e7692c327bf2bc949 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +robot = robot.run:run_cli + diff --git a/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..0b2c39976c8717f8e6df6093ffdd2aff9d2b649c --- /dev/null +++ b/robot/lib/python3.8/site-packages/RESTinstance-1.0.2.dist-info/top_level.txt @@ -0,0 +1 @@ +REST diff --git a/robot/lib/python3.8/site-packages/RequestsLibrary/DeprecatedKeywords.py b/robot/lib/python3.8/site-packages/RequestsLibrary/DeprecatedKeywords.py new file mode 100644 index 0000000000000000000000000000000000000000..e404ff17c4c4a569d2c02c73af385ba1e4041d8f --- /dev/null +++ b/robot/lib/python3.8/site-packages/RequestsLibrary/DeprecatedKeywords.py @@ -0,0 +1,378 @@ +import json + +from robot.api import logger +from robot.api.deco import keyword + +from RequestsLibrary import utils +from RequestsLibrary.compat import PY3 + +from .SessionKeywords import SessionKeywords + + +class DeprecatedKeywords(SessionKeywords): + + @keyword("To Json") + def to_json(self, content, pretty_print=False): + """ + *DEPRECATED* Please use ${resp.json()} instead. Have a look at the improved HTML output as pretty printing replacement. + + Convert a string to a JSON object + + ``content`` String content to convert into JSON + + ``pretty_print`` If defined, will output JSON is pretty print format + """ + if PY3: + if isinstance(content, bytes): + content = content.decode(encoding='utf-8') + if pretty_print: + json_ = utils.json_pretty_print(content) + else: + json_ = json.loads(content) + logger.info('To JSON using : content=%s ' % (content)) + logger.info('To JSON using : pretty_print=%s ' % (pretty_print)) + return json_ + + def get_request( + self, + alias, + uri, + headers=None, + data=None, + json=None, + params=None, + allow_redirects=None, + timeout=None): + """ + *DEPRECATED* Please use `GET On Session` instead. + + Send a GET request on the session object found using the + given `alias` + + ``alias`` that will be used to identify the Session object in the cache + + ``uri`` to send the GET request to + + ``params`` url parameters to append to the uri + + ``headers`` a dictionary of headers to use with the request + + ``data`` a dictionary of key-value pairs that will be urlencoded + and sent as GET data + or binary data that is sent as the raw body content + + ``json`` a value that will be json encoded + and sent as GET data if data is not specified + + ``allow_redirects`` Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. + + ``timeout`` connection timeout + """ + session = self._cache.switch(alias) + # XXX workaround to restore library default behaviour. Not needed in new keywords + redir = True if allow_redirects is None else allow_redirects + + response = self._common_request( + "get", + session, + uri, + params=params, + headers=headers, + data=data, + json=json, + allow_redirects=redir, + timeout=timeout) + + return response + + def post_request( + self, + alias, + uri, + data=None, + json=None, + params=None, + headers=None, + files=None, + allow_redirects=None, + timeout=None): + """ + *DEPRECATED* Please use `POST On Session` instead. + + Send a POST request on the session object found using the + given `alias` + + ``alias`` that will be used to identify the Session object in the cache + + ``uri`` to send the POST request to + + ``data`` a dictionary of key-value pairs that will be urlencoded + and sent as POST data + or binary data that is sent as the raw body content + or passed as such for multipart form data if ``files`` is also defined + or file descriptor retrieved by Get File For Streaming Upload + + ``json`` a value that will be json encoded + and sent as POST data if files or data is not specified + + ``params`` url parameters to append to the uri + + ``headers`` a dictionary of headers to use with the request + + ``files`` a dictionary of file names containing file data to POST to the server + + ``allow_redirects`` Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. + + ``timeout`` connection timeout + """ + session = self._cache.switch(alias) + if not files: + data = utils.format_data_according_to_header(session, data, headers) + # XXX workaround to restore library default behaviour. Not needed in new keywords + redir = True if allow_redirects is None else allow_redirects + + response = self._common_request( + "post", + session, + uri, + data=data, + json=json, + params=params, + files=files, + headers=headers, + allow_redirects=redir, + timeout=timeout) + return response + + def patch_request( + self, + alias, + uri, + data=None, + json=None, + params=None, + headers=None, + files=None, + allow_redirects=None, + timeout=None): + """ + *DEPRECATED* Please use `PATCH On Session` instead. + + Send a PATCH request on the session object found using the + given `alias` + + ``alias`` that will be used to identify the Session object in the cache + + ``uri`` to send the PATCH request to + + ``data`` a dictionary of key-value pairs that will be urlencoded + and sent as PATCH data + or binary data that is sent as the raw body content + or file descriptor retrieved by Get File For Streaming Upload + + ``json`` a value that will be json encoded + and sent as PATCH data if data is not specified + + ``headers`` a dictionary of headers to use with the request + + ``files`` a dictionary of file names containing file data to PATCH to the server + + ``allow_redirects`` Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. + + ``params`` url parameters to append to the uri + + ``timeout`` connection timeout + """ + session = self._cache.switch(alias) + data = utils.format_data_according_to_header(session, data, headers) + # XXX workaround to restore library default behaviour. Not needed in new keywords + redir = True if allow_redirects is None else allow_redirects + + response = self._common_request( + "patch", + session, + uri, + data=data, + json=json, + params=params, + files=files, + headers=headers, + allow_redirects=redir, + timeout=timeout) + + return response + + def put_request( + self, + alias, + uri, + data=None, + json=None, + params=None, + files=None, + headers=None, + allow_redirects=None, + timeout=None): + """ + *DEPRECATED* Please use `PUT On Session` instead. + + Send a PUT request on the session object found using the + given `alias` + + ``alias`` that will be used to identify the Session object in the cache + + ``uri`` to send the PUT request to + + ``data`` a dictionary of key-value pairs that will be urlencoded + and sent as PUT data + or binary data that is sent as the raw body content + or file descriptor retrieved by Get File For Streaming Upload + + ``json`` a value that will be json encoded + and sent as PUT data if data is not specified + + ``headers`` a dictionary of headers to use with the request + + ``allow_redirects`` Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. + + ``params`` url parameters to append to the uri + + ``timeout`` connection timeout + """ + session = self._cache.switch(alias) + data = utils.format_data_according_to_header(session, data, headers) + # XXX workaround to restore library default behaviour. Not needed in new keywords + redir = True if allow_redirects is None else allow_redirects + + response = self._common_request( + "put", + session, + uri, + data=data, + json=json, + params=params, + files=files, + headers=headers, + allow_redirects=redir, + timeout=timeout) + + return response + + def delete_request( + self, + alias, + uri, + data=None, + json=None, + params=None, + headers=None, + allow_redirects=None, + timeout=None): + """ + *DEPRECATED* Please use `DELETE On Session` instead. + + Send a DELETE request on the session object found using the + given `alias` + + ``alias`` that will be used to identify the Session object in the cache + + ``uri`` to send the DELETE request to + + ``json`` a value that will be json encoded + and sent as request data if data is not specified + + ``headers`` a dictionary of headers to use with the request + + ``allow_redirects`` Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. + + ``timeout`` connection timeout + """ + session = self._cache.switch(alias) + data = utils.format_data_according_to_header(session, data, headers) + # XXX workaround to restore library default behaviour. Not needed in new keywords + redir = True if allow_redirects is None else allow_redirects + + response = self._common_request( + "delete", + session, + uri, + data=data, + json=json, + params=params, + headers=headers, + allow_redirects=redir, + timeout=timeout) + + return response + + def head_request( + self, + alias, + uri, + headers=None, + allow_redirects=None, + timeout=None): + """ + *DEPRECATED* Please use `HEAD On Session` instead. + + Send a HEAD request on the session object found using the + given `alias` + + ``alias`` that will be used to identify the Session object in the cache + + ``uri`` to send the HEAD request to + + ``allow_redirects`` Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. + + ``headers`` a dictionary of headers to use with the request + + ``timeout`` connection timeout + """ + session = self._cache.switch(alias) + # XXX workaround to restore library default behaviour. Not needed in new keywords + redir = False if allow_redirects is None else allow_redirects + response = self._common_request( + "head", + session, + uri, + headers=headers, + allow_redirects=redir, + timeout=timeout) + + return response + + def options_request( + self, + alias, + uri, + headers=None, + allow_redirects=None, + timeout=None): + """ + *DEPRECATED* Please use `OPTIONS On Session` instead. + + Send an OPTIONS request on the session object found using the + given `alias` + + ``alias`` that will be used to identify the Session object in the cache + + ``uri`` to send the OPTIONS request to + + ``allow_redirects`` Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. + + ``headers`` a dictionary of headers to use with the request + + ``timeout`` connection timeout + """ + session = self._cache.switch(alias) + # XXX workaround to restore library default behaviour. Not needed in new keywords + redir = True if allow_redirects is None else allow_redirects + response = self._common_request( + "options", + session, + uri, + headers=headers, + allow_redirects=redir, + timeout=timeout) + + return response diff --git a/robot/lib/python3.8/site-packages/RequestsLibrary/RequestsKeywords.py b/robot/lib/python3.8/site-packages/RequestsLibrary/RequestsKeywords.py new file mode 100644 index 0000000000000000000000000000000000000000..a98b7c26565ca03667e22f9d2cf07fdf26df31fe --- /dev/null +++ b/robot/lib/python3.8/site-packages/RequestsLibrary/RequestsKeywords.py @@ -0,0 +1,66 @@ +import json + +import robot +from robot.api.deco import keyword +from robot.libraries.BuiltIn import BuiltIn +from robot.utils.asserts import assert_equal + + +class RequestsKeywords(object): + ROBOT_LIBRARY_SCOPE = 'Global' + + def __init__(self): + self._cache = robot.utils.ConnectionCache('No sessions created') + self.builtin = BuiltIn() + self.debug = 0 + + @keyword("Status Should Be") + def status_should_be(self, expected_status, response, msg=None): + """ + Fails if response status code is different than the expected. + + ``expected_status`` could be the code number as an integer or as string. + But it could also be a named status code like 'ok', 'created', 'accepted' or + 'bad request', 'not found' etc. + + ``response`` is the output of other requests keywords like `GET On Session`. + + In case of failure an HTTPError will be automatically raised. + A custom failure message ``msg`` can be added like in built-in keywords. + + `* On Session` keywords (like `GET On Session`) already have an implicit assert mechanism, that by default, + verifies the response status code. + `Status Should Be` keyword can be useful to do an explicit assert in case of `* On Session` keyword with + ``expected_status=anything`` to disable implicit assert. + """ + # TODO add an example in documentation of GET On Session expected=any than assert + + self._check_status(expected_status, response, msg) + + @keyword("Request Should Be Successful") + def request_should_be_successful(self, response): + """ + Fails if response status code is a client or server error (4xx, 5xx). + + ``response`` is the output of other requests keywords like `GET On Session`. + + In case of failure an HTTPError will be automatically raised. + A custom failure message ``msg`` can be added like in built-in keywords. + + For a more versatile assert keyword see `Status Should Be`. + """ + self._check_status(None, response, msg=None) + + @staticmethod + @keyword("Get File For Streaming Upload") + def get_file_for_streaming_upload(path): + """ + Opens and returns a file descriptor of a specified file to be passed as ``data`` parameter + to other requests keywords. + + This allows streaming upload of large files without reading them into memory. + + File descriptor is binary mode and read only. Requests keywords will automatically close the file, + if used outside this library it's up to the caller to close it. + """ + return open(path, 'rb') diff --git a/robot/lib/python3.8/site-packages/RequestsLibrary/RequestsOnSessionKeywords.py b/robot/lib/python3.8/site-packages/RequestsLibrary/RequestsOnSessionKeywords.py new file mode 100644 index 0000000000000000000000000000000000000000..d48e065926d3a817b30bf08b9048a37137b0988f --- /dev/null +++ b/robot/lib/python3.8/site-packages/RequestsLibrary/RequestsOnSessionKeywords.py @@ -0,0 +1,213 @@ +from robot.api.deco import keyword + +from RequestsLibrary.utils import warn_if_equal_symbol_in_url +from .SessionKeywords import SessionKeywords + + +class RequestsOnSessionKeywords(SessionKeywords): + + @warn_if_equal_symbol_in_url + @keyword("GET On Session") + def get_on_session(self, alias, url, params=None, + expected_status=None, msg=None, **kwargs): + """ + Sends a GET request on a previously created HTTP Session. + + Session will be identified using the ``alias`` name. + The endpoint used to retrieve the resource is the ``url``, while query + string parameters can be passed as string, dictionary (or list of tuples or bytes) + through the ``params``. + + By default this keyword fails if a status code with error values is returned in the response, + this behavior can be modified using the ``expected_status`` and ``msg`` parameters, + read more about it in `Status Should Be` keyword documentation. + In order to disable this implicit assert mechanism you can pass as ``expected_status`` the values ``any`` or + ``anything``. + + Other optional requests arguments can be passed using ``**kwargs`` here is a list: + + | ``data`` | Dictionary, list of tuples, bytes, or file-like object to send in the body of the request. | + | ``json`` | A JSON serializable Python object to send in the body of the request. | + | ``headers`` | Dictionary of HTTP Headers to send with the request. | + | ``cookies`` | Dict or CookieJar object to send with the request. | + | ``files`` | Dictionary of file-like-objects (or ``{'name': file-tuple}``) for multipart encoding upload. | + | ``file-tuple`` | can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers to add for the file. | + | ``auth`` | Auth tuple to enable Basic/Digest/Custom HTTP Auth. | + | ``timeout`` | How many seconds to wait for the server to send data before giving up, as a float, or a ``(connect timeout, read timeout)`` tuple. | + | ``allow_redirects`` | Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. | + | ``proxies`` | Dictionary mapping protocol to the URL of the proxy. | + | ``verify`` | Either a boolean, in which case it controls whether we verify the server's TLS certificate, or a string, in which case it must be a path to a CA bundle to use. Defaults to ``True``. | + | ``stream`` | if ``False``, the response content will be immediately downloaded. | + | ``cert`` | if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. | + + For more updated and complete information verify the official Requests api documentation: + https://requests.readthedocs.io/en/latest/api/ + + """ + session = self._cache.switch(alias) + response = self._common_request("get", session, url, + params=params, **kwargs) + self._check_status(expected_status, response, msg) + return response + + @warn_if_equal_symbol_in_url + @keyword("POST On Session") + def post_on_session(self, alias, url, data=None, json=None, + expected_status=None, msg=None, **kwargs): + """ + Sends a POST request on a previously created HTTP Session. + + Session will be identified using the ``alias`` name. + The endpoint used to send the request is the ``url`` parameter, while its body + can be passed using ``data`` or ``json`` parameters. + + ``data`` can be a dictionary, list of tuples, bytes, or file-like object. + If you want to pass a json body pass a dictionary as ``json`` parameter. + + By default this keyword fails if a status code with error values is returned in the response, + this behavior can be modified using the ``expected_status`` and ``msg`` parameters, + read more about it in `Status Should Be` keyword documentation. + In order to disable this implicit assert mechanism you can pass as ``expected_status`` the values ``any`` or + ``anything``. + + Other optional requests arguments can be passed using ``**kwargs`` + see the `GET On Session` keyword for the complete list. + """ + session = self._cache.switch(alias) + response = self._common_request("post", session, url, + data=data, json=json, **kwargs) + self._check_status(expected_status, response, msg) + return response + + @warn_if_equal_symbol_in_url + @keyword("PATCH On Session") + def patch_on_session(self, alias, url, data=None, json=None, + expected_status=None, msg=None, **kwargs): + """ + Sends a PATCH request on a previously created HTTP Session. + + Session will be identified using the ``alias`` name. + The endpoint used to send the request is the ``url`` parameter, while its body + can be passed using ``data`` or ``json`` parameters. + + ``data`` can be a dictionary, list of tuples, bytes, or file-like object. + If you want to pass a json body pass a dictionary as ``json`` parameter. + + By default this keyword fails if a status code with error values is returned in the response, + this behavior can be modified using the ``expected_status`` and ``msg`` parameters, + read more about it in `Status Should Be` keyword documentation. + In order to disable this implicit assert mechanism you can pass as ``expected_status`` the values ``any`` or + ``anything``. + + Other optional requests arguments can be passed using ``**kwargs`` + see the `GET On Session` keyword for the complete list. + """ + session = self._cache.switch(alias) + response = self._common_request("patch", session, url, + data=data, json=json, **kwargs) + self._check_status(expected_status, response, msg) + return response + + @warn_if_equal_symbol_in_url + @keyword("PUT On Session") + def put_on_session(self, alias, url, data=None, json=None, + expected_status=None, msg=None, **kwargs): + """ + Sends a PUT request on a previously created HTTP Session. + + Session will be identified using the ``alias`` name. + The endpoint used to send the request is the ``url`` parameter, while its body + can be passed using ``data`` or ``json`` parameters. + + ``data`` can be a dictionary, list of tuples, bytes, or file-like object. + If you want to pass a json body pass a dictionary as ``json`` parameter. + + By default this keyword fails if a status code with error values is returned in the response, + this behavior can be modified using the ``expected_status`` and ``msg`` parameters, + read more about it in `Status Should Be` keyword documentation. + In order to disable this implicit assert mechanism you can pass as ``expected_status`` the values ``any`` or + ``anything``. + + Other optional requests arguments can be passed using ``**kwargs`` + see the `GET On Session` keyword for the complete list. + """ + session = self._cache.switch(alias) + response = self._common_request("put", session, url, + data=data, json=json, **kwargs) + self._check_status(expected_status, response, msg) + return response + + @warn_if_equal_symbol_in_url + @keyword('DELETE On Session') + def delete_on_session(self, alias, url, + expected_status=None, msg=None, **kwargs): + """ + Sends a DELETE request on a previously created HTTP Session. + + Session will be identified using the ``alias`` name. + The endpoint used to send the request is the ``url`` parameter. + + By default this keyword fails if a status code with error values is returned in the response, + this behavior can be modified using the ``expected_status`` and ``msg`` parameters, + read more about it in `Status Should Be` keyword documentation. + In order to disable this implicit assert mechanism you can pass as ``expected_status`` the values ``any`` or + ``anything``. + + Other optional requests arguments can be passed using ``**kwargs`` + see the `GET On Session` keyword for the complete list. + """ + session = self._cache.switch(alias) + response = self._common_request("delete", session, url, **kwargs) + self._check_status(expected_status, response, msg) + return response + + @warn_if_equal_symbol_in_url + @keyword("HEAD On Session") + def head_on_session(self, alias, url, + expected_status=None, msg=None, **kwargs): + """ + Sends a HEAD request on a previously created HTTP Session. + + Session will be identified using the ``alias`` name. + The endpoint used to retrieve the HTTP headers is the ``url``. + + ``allow_redirects`` parameter is not provided, it will be set to `False` (as + opposed to the default behavior). + + By default this keyword fails if a status code with error values is returned in the response, + this behavior can be modified using the ``expected_status`` and ``msg`` parameters, + read more about it in `Status Should Be` keyword documentation. + In order to disable this implicit assert mechanism you can pass as ``expected_status`` the values ``any`` or + ``anything``. + + Other optional requests arguments can be passed using ``**kwargs`` + see the `GET On Session` keyword for the complete list. + """ + session = self._cache.switch(alias) + response = self._common_request("head", session, url, **kwargs) + self._check_status(expected_status, response, msg) + return response + + @warn_if_equal_symbol_in_url + @keyword("OPTIONS On Session") + def options_on_session(self, alias, url, + expected_status=None, msg=None, **kwargs): + """ + Sends a OPTIONS request on a previously created HTTP Session. + + Session will be identified using the ``alias`` name. + The endpoint used to retrieve the resource is the ``url``. + + By default this keyword fails if a status code with error values is returned in the response, + this behavior can be modified using the ``expected_status`` and ``msg`` parameters, + read more about it in `Status Should Be` keyword documentation. + In order to disable this implicit assert mechanism you can pass as ``expected_status`` the values ``any`` or + ``anything``. + + Other optional requests arguments can be passed using ``**kwargs`` + see the `GET On Session` keyword for the complete list. + """ + session = self._cache.switch(alias) + response = self._common_request("options", session, url, **kwargs) + self._check_status(expected_status, response, msg) + return response diff --git a/robot/lib/python3.8/site-packages/RequestsLibrary/SessionKeywords.py b/robot/lib/python3.8/site-packages/RequestsLibrary/SessionKeywords.py new file mode 100644 index 0000000000000000000000000000000000000000..c821320ad8620f540d17c9ca1d32c79ef889bea2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RequestsLibrary/SessionKeywords.py @@ -0,0 +1,651 @@ +import sys +import logging + +import requests +from requests.cookies import merge_cookies +from requests.sessions import merge_setting +from requests.models import Response + +from robot.api import logger +from robot.api.deco import keyword +from robot.utils.asserts import assert_equal + +from RequestsLibrary import utils, log +from RequestsLibrary.compat import httplib, PY3, RetryAdapter +from .RequestsKeywords import RequestsKeywords +from RequestsLibrary.exceptions import InvalidResponse, InvalidExpectedStatus +from RequestsLibrary.utils import is_file_descriptor, is_string_type + +try: + from requests_ntlm import HttpNtlmAuth +except ImportError: + pass + + +class SessionKeywords(RequestsKeywords): + DEFAULT_RETRY_METHOD_LIST = RetryAdapter.get_default_allowed_methods() + + def _create_session( + self, + alias, + url, + headers, + cookies, + auth, + timeout, + proxies, + verify, + debug, + max_retries, + backoff_factor, + disable_warnings, + retry_status_list, + retry_method_list): + + logger.debug('Creating session: %s' % alias) + s = session = requests.Session() + s.headers.update(headers) + s.auth = auth if auth else s.auth + s.proxies = proxies if proxies else s.proxies + + try: + max_retries = int(max_retries) + retry_status_list = [int(x) for x in retry_status_list] if retry_status_list else None + except ValueError as err: + raise ValueError("Error converting session parameter: %s" % err) + + if max_retries > 0: + retry = RetryAdapter(total=max_retries, + backoff_factor=backoff_factor, + status_forcelist=retry_status_list, + allowed_methods=retry_method_list) + http = requests.adapters.HTTPAdapter(max_retries=retry) + https = requests.adapters.HTTPAdapter(max_retries=retry) + + # Replace the session's original adapters + s.mount('http://', http) + s.mount('https://', https) + + # Disable requests warnings, useful when you have large number of testcase + # you will observe drastical changes in Robot log.html and output.xml files size + if disable_warnings: + # you need to initialize logging, otherwise you will not see anything from requests + logging.basicConfig() + logging.getLogger().setLevel(logging.ERROR) + requests_log = logging.getLogger("requests") + requests_log.setLevel(logging.ERROR) + requests_log.propagate = True + if not verify: + requests.packages.urllib3.disable_warnings() + + # verify can be a Boolean or a String + if isinstance(verify, bool): + s.verify = verify + elif utils.is_string_type(verify): + if verify.lower() == 'true' or verify.lower() == 'false': + s.verify = self.builtin.convert_to_boolean(verify) + else: + # String for CA_BUNDLE, not a Boolean String + s.verify = verify + else: + # not a Boolean nor a String + s.verify = verify + + # cant pass these into the Session anymore + self.timeout = float(timeout) if timeout is not None else None + self.cookies = cookies + self.verify = verify if self.builtin.convert_to_boolean(verify) is not True else None + + s.url = url + + # Enable http verbosity + if int(debug) >= 1: + self.debug = int(debug) + httplib.HTTPConnection.debuglevel = self.debug + + self._cache.register(session, alias=alias) + return session + + @keyword("Create Session") + def create_session(self, + alias, + url, + headers={}, + cookies={}, + auth=None, + timeout=None, + proxies=None, + verify=False, + debug=0, + max_retries=3, + backoff_factor=0.10, + disable_warnings=0, + retry_status_list=[], + retry_method_list=DEFAULT_RETRY_METHOD_LIST): + """ Create Session: create a HTTP session to a server + + ``alias`` Robot Framework alias to identify the session + + ``url`` Base url of the server + + ``headers`` Dictionary of default headers + + ``cookies`` Dictionary of cookies + + ``auth`` List of username & password for HTTP Basic Auth + + ``timeout`` Connection timeout + + ``proxies`` Dictionary that contains proxy urls for HTTP and HTTPS communication + + ``verify`` Whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. + + ``debug`` Enable http verbosity option more information + https://docs.python.org/2/library/httplib.html#httplib.HTTPConnection.set_debuglevel + + ``max_retries`` Number of maximum retries each connection should attempt. + By default it will retry 3 times in case of connection errors only. + A 0 value will disable any kind of retries regardless of other retry settings. + In case the number of retries is reached a retry exception is raised. + + ``disable_warnings`` Disable requests warning useful when you have large number of testcases + + ``backoff_factor`` Introduces a delay time between retries that is longer after each retry. + eg. if backoff_factor is set to 0.1 + the sleep between attemps will be: 0.0, 0.2, 0.4 + More info here: https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html + + ``retry_method_list`` List of uppercased HTTP method verbs where retries are allowed. + By default retries are allowed only on HTTP requests methods that are considered to be + idempotent (multiple requests with the same parameters end with the same state). + eg. set to ['POST', 'GET'] to retry only those kind of requests. + + ``retry_status_list`` List of integer HTTP status codes that, if returned, a retry is attempted. + eg. set to [502, 503] to retry requests if those status are returned. + Note that max_retries must be greater than 0. + + """ + auth = requests.auth.HTTPBasicAuth(*auth) if auth else None + + logger.info('Creating Session using : alias=%s, url=%s, headers=%s, \ + cookies=%s, auth=%s, timeout=%s, proxies=%s, verify=%s, \ + debug=%s ' % (alias, url, headers, cookies, auth, timeout, + proxies, verify, debug)) + return self._create_session( + alias=alias, + url=url, + headers=headers, + cookies=cookies, + auth=auth, + timeout=timeout, + max_retries=max_retries, + backoff_factor=backoff_factor, + proxies=proxies, + verify=verify, + debug=debug, + disable_warnings=disable_warnings, + retry_status_list=retry_status_list, + retry_method_list=retry_method_list) + + @keyword("Create Client Cert Session") + def create_client_cert_session( + self, + alias, + url, + headers={}, + cookies={}, + client_certs=None, + timeout=None, + proxies=None, + verify=False, + debug=0, + max_retries=3, + backoff_factor=0.10, + disable_warnings=0, + retry_status_list=[], + retry_method_list=DEFAULT_RETRY_METHOD_LIST): + """ Create Session: create a HTTP session to a server + + ``url`` Base url of the server + + ``alias`` Robot Framework alias to identify the session + + ``headers`` Dictionary of default headers + + ``cookies`` Dictionary of cookies + + ``client_certs`` ['client certificate', 'client key'] PEM files containing the client key and certificate + + ``timeout`` Connection timeout + + ``proxies`` Dictionary that contains proxy urls for HTTP and HTTPS communication + + ``verify`` Whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. + Defaults to False. + + ``debug`` Enable http verbosity option more information + https://docs.python.org/2/library/httplib.html#httplib.HTTPConnection.set_debuglevel + + ``max_retries`` Number of maximum retries each connection should attempt. + By default it will retry 3 times in case of connection errors only. + A 0 value will disable any kind of retries regardless of other retry settings. + In case the number of retries is reached a retry exception is raised. + + ``disable_warnings`` Disable requests warning useful when you have large number of testcases + + ``backoff_factor`` Introduces a delay time between retries that is longer after each retry. + eg. if backoff_factor is set to 0.1 + the sleep between attemps will be: 0.0, 0.2, 0.4 + More info here: https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html + + ``retry_method_list`` List of uppercased HTTP method verbs where retries are allowed. + By default retries are allowed only on HTTP requests methods that are considered to be + idempotent (multiple requests with the same parameters end with the same state). + eg. set to ['POST', 'GET'] to retry only those kind of requests. + + ``retry_status_list`` List of integer HTTP status codes that, if returned, a retry is attempted. + eg. set to [502, 503] to retry requests if those status are returned. + Note that max_retries must be greater than 0. + """ + + logger.info('Creating Session using : alias=%s, url=%s, headers=%s, \ + cookies=%s, client_certs=%s, timeout=%s, proxies=%s, verify=%s, \ + debug=%s ' % (alias, url, headers, cookies, client_certs, timeout, + proxies, verify, debug)) + + session = self._create_session( + alias=alias, + url=url, + headers=headers, + cookies=cookies, + auth=None, + timeout=timeout, + max_retries=max_retries, + backoff_factor=backoff_factor, + proxies=proxies, + verify=verify, + debug=debug, + disable_warnings=disable_warnings, + retry_status_list=retry_status_list, + retry_method_list=retry_method_list) + + session.cert = tuple(client_certs) + return session + + @keyword("Create Custom Session") + def create_custom_session( + self, + alias, + url, + auth, + headers={}, + cookies={}, + timeout=None, + proxies=None, + verify=False, + debug=0, + max_retries=3, + backoff_factor=0.10, + disable_warnings=0, + retry_status_list=[], + retry_method_list=DEFAULT_RETRY_METHOD_LIST): + """ Create Session: create a HTTP session to a server + + ``url`` Base url of the server + + ``alias`` Robot Framework alias to identify the session + + ``headers`` Dictionary of default headers + + ``cookies`` Dictionary of cookies + + ``auth`` A Custom Authentication object to be passed on to the requests library. + http://docs.python-requests.org/en/master/user/advanced/#custom-authentication + + ``timeout`` Connection timeout + + ``proxies`` Dictionary that contains proxy urls for HTTP and HTTPS communication + + ``verify`` Whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. + Defaults to False. + + ``debug`` Enable http verbosity option more information + https://docs.python.org/2/library/httplib.html#httplib.HTTPConnection.set_debuglevel + + ``max_retries`` Number of maximum retries each connection should attempt. + By default it will retry 3 times in case of connection errors only. + A 0 value will disable any kind of retries regardless of other retry settings. + In case the number of retries is reached a retry exception is raised. + + ``disable_warnings`` Disable requests warning useful when you have large number of testcases + + ``backoff_factor`` Introduces a delay time between retries that is longer after each retry. + eg. if backoff_factor is set to 0.1 + the sleep between attemps will be: 0.0, 0.2, 0.4 + More info here: https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html + + ``retry_method_list`` List of uppercased HTTP method verbs where retries are allowed. + By default retries are allowed only on HTTP requests methods that are considered to be + idempotent (multiple requests with the same parameters end with the same state). + eg. set to ['POST', 'GET'] to retry only those kind of requests. + + ``retry_status_list`` List of integer HTTP status codes that, if returned, a retry is attempted. + eg. set to [502, 503] to retry requests if those status are returned. + Note that max_retries must be greater than 0. + """ + + logger.info('Creating Custom Authenticated Session using : alias=%s, url=%s, headers=%s, \ + cookies=%s, auth=%s, timeout=%s, proxies=%s, verify=%s, \ + debug=%s ' % (alias, url, headers, cookies, auth, timeout, + proxies, verify, debug)) + + return self._create_session( + alias=alias, + url=url, + headers=headers, + cookies=cookies, + auth=auth, + timeout=timeout, + max_retries=max_retries, + backoff_factor=backoff_factor, + proxies=proxies, + verify=verify, + debug=debug, + disable_warnings=disable_warnings, + retry_status_list=retry_status_list, + retry_method_list=retry_method_list) + + @keyword("Create Digest Session") + def create_digest_session( + self, + alias, + url, + auth, + headers={}, + cookies={}, + timeout=None, + proxies=None, verify=False, + debug=0, + max_retries=3, + backoff_factor=0.10, + disable_warnings=0, + retry_status_list=[], + retry_method_list=DEFAULT_RETRY_METHOD_LIST): + """ Create Session: create a HTTP session to a server + + ``url`` Base url of the server + + ``alias`` Robot Framework alias to identify the session + + ``headers`` Dictionary of default headers + + ``cookies`` Dictionary of cookies + + ``auth`` ['DOMAIN', 'username', 'password'] for NTLM Authentication + + ``timeout`` Connection timeout + + ``proxies`` Dictionary that contains proxy urls for HTTP and HTTPS communication + + ``verify`` Whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. + Defaults to False. + + ``debug`` Enable http verbosity option more information + https://docs.python.org/2/library/httplib.html#httplib.HTTPConnection.set_debuglevel + + ``max_retries`` Number of maximum retries each connection should attempt. + By default it will retry 3 times in case of connection errors only. + A 0 value will disable any kind of retries regardless of other retry settings. + In case the number of retries is reached a retry exception is raised. + + ``disable_warnings`` Disable requests warning useful when you have large number of testcases + + ``backoff_factor`` Introduces a delay time between retries that is longer after each retry. + eg. if backoff_factor is set to 0.1 + the sleep between attemps will be: 0.0, 0.2, 0.4 + More info here: https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html + + ``retry_method_list`` List of uppercased HTTP method verbs where retries are allowed. + By default retries are allowed only on HTTP requests methods that are considered to be + idempotent (multiple requests with the same parameters end with the same state). + eg. set to ['POST', 'GET'] to retry only those kind of requests. + + ``retry_status_list`` List of integer HTTP status codes that, if returned, a retry is attempted. + eg. set to [502, 503] to retry requests if those status are returned. + Note that max_retries must be greater than 0. + """ + digest_auth = requests.auth.HTTPDigestAuth(*auth) if auth else None + + return self._create_session( + alias=alias, + url=url, + headers=headers, + cookies=cookies, + auth=digest_auth, + timeout=timeout, + max_retries=max_retries, + backoff_factor=backoff_factor, + proxies=proxies, + verify=verify, + debug=debug, + disable_warnings=disable_warnings, + retry_status_list=retry_status_list, + retry_method_list=retry_method_list) + + @keyword("Create Ntlm Session") + def create_ntlm_session( + self, + alias, + url, + auth, + headers={}, + cookies={}, + timeout=None, + proxies=None, + verify=False, + debug=0, + max_retries=3, + backoff_factor=0.10, + disable_warnings=0, + retry_status_list=[], + retry_method_list=DEFAULT_RETRY_METHOD_LIST): + """ Create Session: create a HTTP session to a server + + ``url`` Base url of the server + + ``alias`` Robot Framework alias to identify the session + + ``headers`` Dictionary of default headers + + ``cookies`` Dictionary of cookies + + ``auth`` ['DOMAIN', 'username', 'password'] for NTLM Authentication + + ``timeout`` Connection timeout + + ``proxies`` Dictionary that contains proxy urls for HTTP and HTTPS communication + + ``verify`` Whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. + Defaults to False. + + ``debug`` Enable http verbosity option more information + https://docs.python.org/2/library/httplib.html#httplib.HTTPConnection.set_debuglevel + + ``max_retries`` Number of maximum retries each connection should attempt. + By default it will retry 3 times in case of connection errors only. + A 0 value will disable any kind of retries regardless of other retry settings. + In case the number of retries is reached a retry exception is raised. + + ``disable_warnings`` Disable requests warning useful when you have large number of testcases + + ``backoff_factor`` Introduces a delay time between retries that is longer after each retry. + eg. if backoff_factor is set to 0.1 + the sleep between attemps will be: 0.0, 0.2, 0.4 + More info here: https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html + + ``retry_method_list`` List of uppercased HTTP method verbs where retries are allowed. + By default retries are allowed only on HTTP requests methods that are considered to be + idempotent (multiple requests with the same parameters end with the same state). + eg. set to ['POST', 'GET'] to retry only those kind of requests. + + ``retry_status_list`` List of integer HTTP status codes that, if returned, a retry is attempted. + eg. set to [502, 503] to retry requests if those status are returned. + Note that max_retries must be greater than 0. + """ + try: + HttpNtlmAuth + except NameError: + raise AssertionError('requests_ntlm module not installed') + if len(auth) != 3: + raise AssertionError('Incorrect number of authentication arguments' + ' - expected 3, got {}'.format(len(auth))) + else: + ntlm_auth = HttpNtlmAuth('{}\\{}'.format(auth[0], auth[1]), + auth[2]) + logger.info('Creating NTLM Session using : alias=%s, url=%s, \ + headers=%s, cookies=%s, ntlm_auth=%s, timeout=%s, \ + proxies=%s, verify=%s, debug=%s ' + % (alias, url, headers, cookies, ntlm_auth, + timeout, proxies, verify, debug)) + + return self._create_session( + alias=alias, + url=url, + headers=headers, + cookies=cookies, + auth=ntlm_auth, + timeout=timeout, + max_retries=max_retries, + backoff_factor=backoff_factor, + proxies=proxies, + verify=verify, + debug=debug, + disable_warnings=disable_warnings, + retry_status_list=retry_status_list, + retry_method_list=retry_method_list) + + @keyword("Session Exists") + def session_exists(self, alias): + """Return True if the session has been already created + + ``alias`` that has been used to identify the Session object in the cache + """ + try: + self._cache[alias] + return True + except RuntimeError: + return False + + @keyword("Delete All Sessions") + def delete_all_sessions(self): + """ Removes all the session objects """ + logger.info('Delete All Sessions') + + self._cache.empty_cache() + + # TODO this is not covered by any tests + @keyword("Update Session") + def update_session(self, alias, headers=None, cookies=None): + """Update Session Headers: update a HTTP Session Headers + + ``alias`` Robot Framework alias to identify the session + + ``headers`` Dictionary of headers merge into session + """ + session = self._cache.switch(alias) + session.headers = merge_setting(headers, session.headers) + session.cookies = merge_cookies(session.cookies, cookies) + + def _common_request( + self, + method, + session, + uri, + **kwargs): + + method_function = getattr(session, method) + self._capture_output() + + resp = method_function( + self._get_url(session, uri), + params=utils.utf8_urlencode(kwargs.pop('params', None)), + timeout=self._get_timeout(kwargs.pop('timeout', None)), + cookies=self.cookies, + verify=self.verify, + **kwargs) + + log.log_request(resp) + self._print_debug() + session.last_resp = resp + log.log_response(resp) + + data = kwargs.get('data', None) + if is_file_descriptor(data): + data.close() + + return resp + + @staticmethod + def _check_status(expected_status, resp, msg=None): + """ + Helper method to check HTTP status + """ + if not isinstance(resp, Response): + raise InvalidResponse(resp) + if expected_status is None: + resp.raise_for_status() + else: + if not is_string_type(expected_status): + raise InvalidExpectedStatus(expected_status) + if expected_status.lower() in ['any', 'anything']: + return + try: + expected_status = int(expected_status) + except ValueError: + expected_status = utils.parse_named_status(expected_status) + msg = '' if msg is None else '{} '.format(msg) + msg = "{}Url: {} Expected status".format(msg, resp.url) + assert_equal(resp.status_code, expected_status, msg) + + @staticmethod + def _get_url(session, uri): + """ + Helper method to get the full url + """ + url = session.url + if uri: + slash = '' if uri.startswith('/') else '/' + url = "%s%s%s" % (session.url, slash, uri) + return url + + # FIXME might be broken we need a test for this + def _get_timeout(self, timeout): + return float(timeout) if timeout is not None else self.timeout + + def _capture_output(self): + if self.debug >= 1: + self.http_log = utils.WritableObject() + sys.stdout = self.http_log + + def _print_debug(self): + if self.debug >= 1: + sys.stdout = sys.__stdout__ # Restore stdout + if PY3: + debug_info = ''.join( + self.http_log.content).replace( + '\\r', + '').replace( + '\'', + '') + else: + debug_info = ''.join( + self.http_log.content).replace( + '\\r', + '').decode('string_escape').replace( + '\'', + '') + + # Remove empty lines + debug_info = "\n".join( + [ll.rstrip() for ll in debug_info.splitlines() if ll.strip()]) + logger.debug(debug_info) diff --git a/robot/lib/python3.8/site-packages/RequestsLibrary/__init__.py b/robot/lib/python3.8/site-packages/RequestsLibrary/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4a11a717bd36667680011f3a7b6fce71e0a06704 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RequestsLibrary/__init__.py @@ -0,0 +1,79 @@ +from .DeprecatedKeywords import DeprecatedKeywords +from .RequestsOnSessionKeywords import RequestsOnSessionKeywords +from .version import VERSION + +""" +** Inheritance structure ** +Not exactly a best practice but forced by the fact that RF libraries +are instance of a class. + +RequestsKeywords (common requests and sessionless keywords) + |_ SessionKeywords (session creation and data) + |_ DeprecatedKeywords (old keywords that need sessions) + |_ RequestsOnSessionKeywords (new keywords that use sessions) + +RequestsLibrary (extends RequestsOnSessionKeywords, DeprecatedKeywords) +""" + + +class RequestsLibrary(RequestsOnSessionKeywords, DeprecatedKeywords): + """ RequestsLibrary is a Robot Framework library aimed to provide HTTP api testing functionalities + by wrapping the well known Python Requests Library. + + == Table of contents == + + %TOC% + + = Usage = + + Before making an HTTP request a new connection needs to be prepared this can be done with `Create Session` + keyword. Then you can execute any `* On Session` keywords below some examples: + + | *** Settings *** + | Library Collections + | Library RequestsLibrary + | + | Suite Setup Create Session jsonplaceholder https://jsonplaceholder.typicode.com + | + | *** Test Cases *** + | + | Get Request Test + | Create Session google http://www.google.com + | + | ${resp_google}= GET On Session google / expected_status=200 + | ${resp_json}= GET On Session jsonplaceholder /posts/1 + | + | Should Be Equal As Strings ${resp_google.reason} OK + | Dictionary Should Contain Value ${resp_json.json()} sunt aut facere repellat provident occaecati excepturi optio reprehenderit + | + | Post Request Test + | &{data}= Create dictionary title=Robotframework requests body=This is a test! userId=1 + | ${resp}= POST On Session jsonplaceholder /posts json=${data} + | + | Status Should Be 201 ${resp} + | Dictionary Should Contain Key ${resp.json()} id + + = Response Object = + + All the HTTP requests keywords (GET, POST, PUT, etc.) return an extremely useful Response object. + The Response object contains a server's response to an HTTP request. + + You can access the different attributes with the dot notation in this way: ``${response.json()}`` or + ``${response.text}``. Below the list of the most useful attributes: + + | = Attributes = | = Explanation = | + | content | Content of the response, in bytes. | + | cookies | A CookieJar of Cookies the server sent back. | + | elapsed | The amount of time elapsed between sending the request and the arrival of the response (as a timedelta). This property specifically measures the time taken between sending the first byte of the request and finishing parsing the headers. It is therefore unaffected by consuming the response content or the value of the stream keyword argument. | + | encoding | Encoding to decode with when accessing ``response.text.`` | + | headers | Case-insensitive Dictionary of Response Headers. For example, ``headers['content-encoding']`` will return the value of a `Content-Encoding' response header. | + | history | A list of Response objects from the history of the Request. Any redirect responses will end up here. The list is sorted from the oldest to the most recent request. | + | json | Returns the json-encoded content of a response, if any. Parameters: ``**kwargs`` - Optional arguments that json.loads takes. Raises: ValueError ? If the response body does not contain valid json. | + | ok | Returns True if status_code is less than 400, False if not. | + | reason | Textual reason of responded HTTP Status, e.g. ``Not Found`` or ``OK``. | + | status_code | Integer Code of responded HTTP Status, e.g. 404 or 200. | + | text | Content of the response, in unicode. If ``response.encoding`` is ``None``, encoding will be guessed using chardet. The encoding of the response content is determined based solely on HTTP headers, following RFC 2616 to the letter. If you can take advantage of non-HTTP knowledge to make a better guess at the encoding, you should set ``response.encoding`` appropriately before accessing this property. | + | url | Final URL location of Response. | + """ + __version__ = VERSION + ROBOT_LIBRARY_SCOPE = 'GLOBAL' diff --git a/robot/lib/python3.8/site-packages/RequestsLibrary/compat.py b/robot/lib/python3.8/site-packages/RequestsLibrary/compat.py new file mode 100644 index 0000000000000000000000000000000000000000..0ad443c0bcaaacb37ba2f348c7c3ff2f0dc56720 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RequestsLibrary/compat.py @@ -0,0 +1,31 @@ +import copy +import sys +from requests.packages.urllib3.util import Retry + +PY3 = sys.version_info > (3,) + +if PY3: + import http.client as httplib # noqa + from urllib.parse import urlencode # noqa +else: + import httplib # noqa + from urllib import urlencode # noqa + + +class RetryAdapter(Retry): + + @staticmethod + def get_default_allowed_methods(): + try: + return list(copy.copy(Retry.DEFAULT_ALLOWED_METHODS)) + except AttributeError: + return list(copy.copy(Retry.DEFAULT_METHOD_WHITELIST)) + + def __init__(self, **kwargs): + try: + super(RetryAdapter, self).__init__(**kwargs) + # FIXME more specific except + except TypeError: + value = kwargs.pop('allowed_methods', None) + kwargs['method_whitelist'] = value + super(RetryAdapter, self).__init__(**kwargs) diff --git a/robot/lib/python3.8/site-packages/RequestsLibrary/exceptions.py b/robot/lib/python3.8/site-packages/RequestsLibrary/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..2a46f7c1a215e10cf6a3a4a72b9f54e95f2a9f06 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RequestsLibrary/exceptions.py @@ -0,0 +1,10 @@ +class UnknownStatusError(Exception): + pass + + +class InvalidResponse(Exception): + pass + + +class InvalidExpectedStatus(Exception): + pass diff --git a/robot/lib/python3.8/site-packages/RequestsLibrary/log.py b/robot/lib/python3.8/site-packages/RequestsLibrary/log.py new file mode 100644 index 0000000000000000000000000000000000000000..a603b6e285d5f16a3ed532fda0e20b3800bc78a3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RequestsLibrary/log.py @@ -0,0 +1,45 @@ +import logging + +from RequestsLibrary.utils import is_file_descriptor +from robot.api import logger + + +LOG_CHAR_LIMIT = 10000 + + +def log_response(response): + logger.info("%s Response : url=%s \n " % (response.request.method.upper(), + response.url) + + "status=%s, reason=%s \n " % (response.status_code, + response.reason) + + "headers=%s \n " % response.headers + + "body=%s \n " % format_data_to_log_string(response.text)) + + +def log_request(response): + request = response.request + if response.history: + original_request = response.history[0].request + redirected = '(redirected) ' + else: + original_request = request + redirected = '' + logger.info("%s Request : " % original_request.method.upper() + + "url=%s %s\n " % (original_request.url, redirected) + + "path_url=%s \n " % original_request.path_url + + "headers=%s \n " % original_request.headers + + "body=%s \n " % format_data_to_log_string(original_request.body)) + + +def format_data_to_log_string(data, limit=LOG_CHAR_LIMIT): + + if not data: + return None + + if is_file_descriptor(data): + return repr(data) + + if len(data) > limit and logging.getLogger().level > 10: + data = "%s... (set the log level to DEBUG or TRACE to see the full content)" % data[:limit] + + return data diff --git a/robot/lib/python3.8/site-packages/RequestsLibrary/utils.py b/robot/lib/python3.8/site-packages/RequestsLibrary/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..70f745d6ca9ec4a683e2a00578bfd253ba59af49 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RequestsLibrary/utils.py @@ -0,0 +1,141 @@ +import io +import json +import types + +from requests.status_codes import codes +from requests.structures import CaseInsensitiveDict +from robot.api import logger + +from RequestsLibrary.compat import urlencode, PY3 +from RequestsLibrary.exceptions import UnknownStatusError + + +class WritableObject: + """ HTTP stream handler """ + + def __init__(self): + self.content = [] + + def write(self, string): + self.content.append(string) + + +def parse_named_status(status_code): + """ + Converts named status from human readable to integer + """ + code = status_code.lower().replace(' ', '_') + code = codes.get(code) + if not code: + raise UnknownStatusError(status_code) + return code + + +def merge_headers(session, headers): + if headers is None: + headers = {} + if session.headers is None: + merged_headers = {} + else: + # Session headers are the default but local headers + # have priority and can override values + merged_headers = session.headers.copy() + + # Make sure merged_headers are CaseInsensitiveDict + if not isinstance(merged_headers, CaseInsensitiveDict): + merged_headers = CaseInsensitiveDict(merged_headers) + + merged_headers.update(headers) + return merged_headers + + +def is_json(data): + try: + json.loads(data) + except (TypeError, ValueError): + return False + return True + + +def json_pretty_print(content): + """ + Pretty print a JSON object + + ``content`` JSON object to pretty print + """ + temp = json.loads(content) + return json.dumps( + temp, + sort_keys=True, + indent=4, + separators=( + ',', + ': ')) + + +def is_string_type(data): + if PY3 and isinstance(data, str): + return True + elif not PY3 and isinstance(data, unicode): # noqa + return True + return False + + +def is_file_descriptor(fd): + if PY3 and isinstance(fd, io.IOBase): + return True + if not PY3 and isinstance(fd, file): # noqa + return True + return False + + +def utf8_urlencode(data): + if is_string_type(data): + return data.encode('utf-8') + + if not isinstance(data, dict): + return data + + utf8_data = {} + for k, v in data.items(): + if is_string_type(v): + v = v.encode('utf-8') + utf8_data[k] = v + return urlencode(utf8_data) + + +def format_data_according_to_header(session, data, headers): + # when data is an open file descriptor we ignore it + if is_file_descriptor(data): + return data + + # Merged headers are already case insensitive + headers = merge_headers(session, headers) + + if data is not None and headers is not None and 'Content-Type' in headers and not is_json(data): + if headers['Content-Type'].find("application/json") != -1: + if not isinstance(data, types.GeneratorType): + if str(data).strip(): + data = json.dumps(data) + elif headers['Content-Type'].find("application/x-www-form-urlencoded") != -1: + data = utf8_urlencode(data) + else: + data = utf8_urlencode(data) + + return data + + +def warn_if_equal_symbol_in_url(func): + def decorator(*args, **kwargs): + try: + args[2] + except IndexError: + if 'url' not in kwargs: + logger.warn("You might have an = symbol in url." + " You better place 'url=' before, example: 'url=/your-url/foo?param=a'" + " or use '/your-url/foo params=param=a' or escape it") + + return func(*args, **kwargs) + decorator.__name__ = func.__name__ + decorator.__doc__ = func.__doc__ + return decorator diff --git a/robot/lib/python3.8/site-packages/RequestsLibrary/version.py b/robot/lib/python3.8/site-packages/RequestsLibrary/version.py new file mode 100644 index 0000000000000000000000000000000000000000..f8428b831abe75bbba4e5137689ff1649a9ff5b3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/RequestsLibrary/version.py @@ -0,0 +1 @@ +VERSION = '0.8.0' diff --git a/robot/lib/python3.8/site-packages/_pyrsistent_version.py b/robot/lib/python3.8/site-packages/_pyrsistent_version.py new file mode 100644 index 0000000000000000000000000000000000000000..e0cd4e2283d025bf91267601a1bf8d5503198464 --- /dev/null +++ b/robot/lib/python3.8/site-packages/_pyrsistent_version.py @@ -0,0 +1 @@ +__version__ = '0.17.3' diff --git a/robot/lib/python3.8/site-packages/_virtualenv.pth b/robot/lib/python3.8/site-packages/_virtualenv.pth new file mode 100644 index 0000000000000000000000000000000000000000..1c3ff99867d8110052195fed7650ec50c1f3adfe --- /dev/null +++ b/robot/lib/python3.8/site-packages/_virtualenv.pth @@ -0,0 +1 @@ +import _virtualenv \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/_virtualenv.py b/robot/lib/python3.8/site-packages/_virtualenv.py new file mode 100644 index 0000000000000000000000000000000000000000..b399da4cc2c6254fecb330943e0e6834a19c27c8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/_virtualenv.py @@ -0,0 +1,115 @@ +"""Patches that are applied at runtime to the virtual environment""" +# -*- coding: utf-8 -*- + +import os +import sys + +VIRTUALENV_PATCH_FILE = os.path.join(__file__) + + +def patch_dist(dist): + """ + Distutils allows user to configure some arguments via a configuration file: + https://docs.python.org/3/install/index.html#distutils-configuration-files + + Some of this arguments though don't make sense in context of the virtual environment files, let's fix them up. + """ + # we cannot allow some install config as that would get packages installed outside of the virtual environment + old_parse_config_files = dist.Distribution.parse_config_files + + def parse_config_files(self, *args, **kwargs): + result = old_parse_config_files(self, *args, **kwargs) + install = self.get_option_dict("install") + + if "prefix" in install: # the prefix governs where to install the libraries + install["prefix"] = VIRTUALENV_PATCH_FILE, os.path.abspath(sys.prefix) + for base in ("purelib", "platlib", "headers", "scripts", "data"): + key = "install_{}".format(base) + if key in install: # do not allow global configs to hijack venv paths + install.pop(key, None) + return result + + dist.Distribution.parse_config_files = parse_config_files + + +# Import hook that patches some modules to ignore configuration values that break package installation in case +# of virtual environments. +_DISTUTILS_PATCH = "distutils.dist", "setuptools.dist" +if sys.version_info > (3, 4): + # https://docs.python.org/3/library/importlib.html#setting-up-an-importer + from importlib.abc import MetaPathFinder + from importlib.util import find_spec + from threading import Lock + from functools import partial + + class _Finder(MetaPathFinder): + """A meta path finder that allows patching the imported distutils modules""" + + fullname = None + lock = Lock() + + def find_spec(self, fullname, path, target=None): + if fullname in _DISTUTILS_PATCH and self.fullname is None: + with self.lock: + self.fullname = fullname + try: + spec = find_spec(fullname, path) + if spec is not None: + # https://www.python.org/dev/peps/pep-0451/#how-loading-will-work + is_new_api = hasattr(spec.loader, "exec_module") + func_name = "exec_module" if is_new_api else "load_module" + old = getattr(spec.loader, func_name) + func = self.exec_module if is_new_api else self.load_module + if old is not func: + try: + setattr(spec.loader, func_name, partial(func, old)) + except AttributeError: + pass # C-Extension loaders are r/o such as zipimporter with +Aakanksha Agrawal <11389424+rasponic@users.noreply.github.com> +Abhinav Sagar <40603139+abhinavsagar@users.noreply.github.com> +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Tse +Adam Wentz +admin +Adrien Morison +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Aleks Bunin +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Alexey Popravka +Alli +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrey Bulgakov +Andrés Delfino <34587441+andresdelfino@users.noreply.github.com> +Andrés Delfino +Andy Freeland +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anuj Godase +AQNOUCH Mohammed +AraHaan +Arindam Choudhury +Armin Ronacher +Artem +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avner Cohen +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Darnell +Ben Hoyt +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Brandon L. Reiss +Brandt Bucher +Brett Randall +Brian Cristante <33549821+brcrista@users.noreply.github.com> +Brian Cristante +Brian Rosner +BrownTruck +Bruno Oliveira +Bruno Renié +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +c22 +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Chih-Hsuan Yen +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris McDonough +Chris Wolfe +Christian Heimes +Christian Oudard +Christopher Hunt +Christopher Snyder +Clark Boylan +Clay McClure +Cody +Cody Soyland +Colin Watson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Curtis Doty +cytolentino +Damian Quiroga +Dan Black +Dan Savilonis +Dan Sully +daniel +Daniel Collins +Daniel Hahler +Daniel Holth +Daniel Jost +Daniel Shaulov +Daniele Esposti +Daniele Procida +Danny Hermes +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Bordeynik +David Caro +David Evans +David Linke +David Pursehouse +David Tucker +David Wales +Davidovich +derwolfe +Desetude +Diego Caraballo +DiegoCaraballo +Dmitry Gladkov +Domen Kožar +Donald Stufft +Dongweiming +Douglas Thor +DrFeathers +Dustin Ingram +Dwayne Bailey +Ed Morley <501702+edmorley@users.noreply.github.com> +Ed Morley +Eitan Adler +ekristina +elainechan +Eli Schwartz +Eli Schwartz +Emil Burzo +Emil Styrke +Endoh Takanao +enoch +Erdinc Mutlu +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Ernest W Durbin III +Ernest W. Durbin III +Erwin Janssen +Eugene Vereshchagin +everdimension +Felix Yan +fiber-space +Filip Kokosiński +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gdanielson +Geoffrey Lehée +Geoffrey Sneddon +George Song +Georgi Valkov +Giftlin Rajaiah +gizmoguy1 +gkdoc <40815324+gkdoc@users.noreply.github.com> +Gopinath M <31352222+mgopi1990@users.noreply.github.com> +GOTO Hayato <3532528+gh640@users.noreply.github.com> +gpiks +Guilherme Espada +Guy Rozendorn +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +Herbert Pfennig +Hsiaoming Yang +Hugo +Hugo Lopes Tavares +Hugo van Kemenade +hugovk +Hynek Schlawack +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ilya Baryshev +INADA Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Ivan Pozdeev +Jacob Kim +jakirkham +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James Cleveland +James Cleveland +James Firth +James Polley +Jan Pokorný +Jannis Leidel +jarondl +Jason R. Coombs +Jay Graves +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jelmer Vernooij +jenix21 +Jeremy Stanley +Jeremy Zafran +Jiashuo Li +Jim Garrison +Jivan Amara +John Paton +John-Scott Atlakson +johnthagen +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joost Molenaar +Jorge Niedbalski +Joseph Long +Josh Bronson +Josh Hansen +Josh Schneier +Juanjo Bazán +Julian Berman +Julian Gethmann +Julien Demoor +jwg4 +Jyrki Pulliainen +Kai Chen +Kamal Bin Mustafa +kaustav haldar +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kexuan Sun +Kit Randel +kpinc +Krishna Oza +Kumar McMillan +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurie Opperman +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +Loren Carvalho +Lucas Cimon +Ludovic Gasc +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +László Kiss Kollár +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark Williams +Mark Williams +Markus Hametner +Masaki +Masklinn +Matej Stuchlik +Mathew Jennings +Mathieu Bridon +Matt Good +Matt Maker +Matt Robenolt +matthew +Matthew Einhorn +Matthew Gilliard +Matthew Iversen +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna <44498973+mbaluna@users.noreply.github.com> +mdebi <17590103+mdebi@users.noreply.github.com> +memoselyk +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Williamson +michaelpacer +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Monica Baluna +montefra +Monty Taylor +Nate Coraor +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nikhil Benesch +Nitesh Sharma +Nowell Strite +NtaleGrey +nvdv +Ofekmeister +ofrinevo +Oliver Jeeves +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +OMOTO Kenji +Omry Yadan +Oren Held +Oscar Benjamin +Oz N Tiram +Pachwenko <32424503+Pachwenko@users.noreply.github.com> +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavithra Eswaramoorthy <33131404+QueenCoffee@users.noreply.github.com> +Pawel Jasinski +Pekka Klärck +Peter Lisák +Peter Waller +petr-tik +Phaneendra Chiruvella +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Pratik Mallya +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal <7895pulkit@gmail.com> +Qiangning Hong +Quentin Pradet +R. David Murray +Rafael Caricio +Ralf Schmitt +Razzi Abuissa +rdb +Remi Rampin +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Richard Jones +RobberPhex +Robert Collins +Robert McGibbon +Robert T. McGibbon +robin elisha robinson +Roey Berman +Rohan Jain +Rohan Jain +Rohan Jain +Roman Bogorodskiy +Romuald Brunet +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +Roy Wellington Ⅳ +Ryan Wooden +ryneeverett +Sachi King +Salvatore Rinchiera +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sergey Vasilyev +Seth Woodworth +Shlomi Fish +Shovan Maity +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +Sorin Sbarnea +Stavros Korokithakis +Stefan Scherfke +Stephan Erb +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +stonebig +Stéphane Bidoul (ACSONE) +Stéphane Bidoul +Stéphane Klein +Sumana Harihareswara +Sviatoslav Sydorenko +Sviatoslav Sydorenko +Swat009 +Takayuki SHIMIZUKAWA +tbeswick +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tom Forbes +Tom Freudenheim +Tom V +Tomas Orsava +Tomer Chachamu +Tony Beswick +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +Travis Swicegood +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Philippon +Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> +Vitaly Babiy +Vladimir Rutsky +W. Trevor King +Wil Tan +Wilfred Hughes +William ML Leslie +William T Olson +Wilson Mo +wim glenn +Wolfgang Maier +Xavier Fernandez +Xavier Fernandez +xoviat +xtreak +YAMAMOTO Takashi +Yen Chi Hsuan +Yeray Diaz Diaz +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Zearin +Zearin +Zhiping Deng +Zvezdan Petkovic +Łukasz Langa +Семён Марьясин diff --git a/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..737fec5c5352af3d9a6a47a0670da4bdb52c5725 --- /dev/null +++ b/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2019 The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/METADATA b/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..56a3d6ea2099dfccf5396bd3f4ebb2531f1da2a0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/METADATA @@ -0,0 +1,256 @@ +Metadata-Version: 2.1 +Name: appdirs +Version: 1.4.3 +Summary: A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir". +Home-page: http://github.com/ActiveState/appdirs +Author: Trent Mick +Author-email: trentm@gmail.com +Maintainer: Trent Mick; Sridhar Ratnakumar; Jeff Rouse +Maintainer-email: trentm@gmail.com; github@srid.name; jr@its.to +License: MIT +Keywords: application directory log cache user +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Topic :: Software Development :: Libraries :: Python Modules + + +.. image:: https://secure.travis-ci.org/ActiveState/appdirs.png + :target: http://travis-ci.org/ActiveState/appdirs + +the problem +=========== + +What directory should your app use for storing user data? If running on Mac OS X, you +should use:: + + ~/Library/Application Support/ + +If on Windows (at least English Win XP) that should be:: + + C:\Documents and Settings\\Application Data\Local Settings\\ + +or possibly:: + + C:\Documents and Settings\\Application Data\\ + +for `roaming profiles `_ but that is another story. + +On Linux (and other Unices) the dir, according to the `XDG +spec `_, is:: + + ~/.local/share/ + + +``appdirs`` to the rescue +========================= + +This kind of thing is what the ``appdirs`` module is for. ``appdirs`` will +help you choose an appropriate: + +- user data dir (``user_data_dir``) +- user config dir (``user_config_dir``) +- user cache dir (``user_cache_dir``) +- site data dir (``site_data_dir``) +- site config dir (``site_config_dir``) +- user log dir (``user_log_dir``) + +and also: + +- is a single module so other Python packages can include their own private copy +- is slightly opinionated on the directory names used. Look for "OPINION" in + documentation and code for when an opinion is being applied. + + +some example output +=================== + +On Mac OS X:: + + >>> from appdirs import * + >>> appname = "SuperApp" + >>> appauthor = "Acme" + >>> user_data_dir(appname, appauthor) + '/Users/trentm/Library/Application Support/SuperApp' + >>> site_data_dir(appname, appauthor) + '/Library/Application Support/SuperApp' + >>> user_cache_dir(appname, appauthor) + '/Users/trentm/Library/Caches/SuperApp' + >>> user_log_dir(appname, appauthor) + '/Users/trentm/Library/Logs/SuperApp' + +On Windows 7:: + + >>> from appdirs import * + >>> appname = "SuperApp" + >>> appauthor = "Acme" + >>> user_data_dir(appname, appauthor) + 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp' + >>> user_data_dir(appname, appauthor, roaming=True) + 'C:\\Users\\trentm\\AppData\\Roaming\\Acme\\SuperApp' + >>> user_cache_dir(appname, appauthor) + 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp\\Cache' + >>> user_log_dir(appname, appauthor) + 'C:\\Users\\trentm\\AppData\\Local\\Acme\\SuperApp\\Logs' + +On Linux:: + + >>> from appdirs import * + >>> appname = "SuperApp" + >>> appauthor = "Acme" + >>> user_data_dir(appname, appauthor) + '/home/trentm/.local/share/SuperApp + >>> site_data_dir(appname, appauthor) + '/usr/local/share/SuperApp' + >>> site_data_dir(appname, appauthor, multipath=True) + '/usr/local/share/SuperApp:/usr/share/SuperApp' + >>> user_cache_dir(appname, appauthor) + '/home/trentm/.cache/SuperApp' + >>> user_log_dir(appname, appauthor) + '/home/trentm/.cache/SuperApp/log' + >>> user_config_dir(appname) + '/home/trentm/.config/SuperApp' + >>> site_config_dir(appname) + '/etc/xdg/SuperApp' + >>> os.environ['XDG_CONFIG_DIRS'] = '/etc:/usr/local/etc' + >>> site_config_dir(appname, multipath=True) + '/etc/SuperApp:/usr/local/etc/SuperApp' + + +``AppDirs`` for convenience +=========================== + +:: + + >>> from appdirs import AppDirs + >>> dirs = AppDirs("SuperApp", "Acme") + >>> dirs.user_data_dir + '/Users/trentm/Library/Application Support/SuperApp' + >>> dirs.site_data_dir + '/Library/Application Support/SuperApp' + >>> dirs.user_cache_dir + '/Users/trentm/Library/Caches/SuperApp' + >>> dirs.user_log_dir + '/Users/trentm/Library/Logs/SuperApp' + + + +Per-version isolation +===================== + +If you have multiple versions of your app in use that you want to be +able to run side-by-side, then you may want version-isolation for these +dirs:: + + >>> from appdirs import AppDirs + >>> dirs = AppDirs("SuperApp", "Acme", version="1.0") + >>> dirs.user_data_dir + '/Users/trentm/Library/Application Support/SuperApp/1.0' + >>> dirs.site_data_dir + '/Library/Application Support/SuperApp/1.0' + >>> dirs.user_cache_dir + '/Users/trentm/Library/Caches/SuperApp/1.0' + >>> dirs.user_log_dir + '/Users/trentm/Library/Logs/SuperApp/1.0' + + + +appdirs Changelog +================= + +appdirs 1.4.3 +------------- +- [PR #76] Python 3.6 invalid escape sequence deprecation fixes +- Fix for Python 3.6 support + +appdirs 1.4.2 +------------- +- [PR #84] Allow installing without setuptools +- [PR #86] Fix string delimiters in setup.py description +- Add Python 3.6 support + +appdirs 1.4.1 +------------- +- [issue #38] Fix _winreg import on Windows Py3 +- [issue #55] Make appname optional + +appdirs 1.4.0 +------------- +- [PR #42] AppAuthor is now optional on Windows +- [issue 41] Support Jython on Windows, Mac, and Unix-like platforms. Windows + support requires `JNA `_. +- [PR #44] Fix incorrect behaviour of the site_config_dir method + +appdirs 1.3.0 +------------- +- [Unix, issue 16] Conform to XDG standard, instead of breaking it for + everybody +- [Unix] Removes gratuitous case mangling of the case, since \*nix-es are + usually case sensitive, so mangling is not wise +- [Unix] Fixes the utterly wrong behaviour in ``site_data_dir``, return result + based on XDG_DATA_DIRS and make room for respecting the standard which + specifies XDG_DATA_DIRS is a multiple-value variable +- [Issue 6] Add ``*_config_dir`` which are distinct on nix-es, according to + XDG specs; on Windows and Mac return the corresponding ``*_data_dir`` + +appdirs 1.2.0 +------------- + +- [Unix] Put ``user_log_dir`` under the *cache* dir on Unix. Seems to be more + typical. +- [issue 9] Make ``unicode`` work on py3k. + +appdirs 1.1.0 +------------- + +- [issue 4] Add ``AppDirs.user_log_dir``. +- [Unix, issue 2, issue 7] appdirs now conforms to `XDG base directory spec + `_. +- [Mac, issue 5] Fix ``site_data_dir()`` on Mac. +- [Mac] Drop use of 'Carbon' module in favour of hardcoded paths; supports + Python3 now. +- [Windows] Append "Cache" to ``user_cache_dir`` on Windows by default. Use + ``opinion=False`` option to disable this. +- Add ``appdirs.AppDirs`` convenience class. Usage: + + >>> dirs = AppDirs("SuperApp", "Acme", version="1.0") + >>> dirs.user_data_dir + '/Users/trentm/Library/Application Support/SuperApp/1.0' + +- [Windows] Cherry-pick Komodo's change to downgrade paths to the Windows short + paths if there are high bit chars. +- [Linux] Change default ``user_cache_dir()`` on Linux to be singular, e.g. + "~/.superapp/cache". +- [Windows] Add ``roaming`` option to ``user_data_dir()`` (for use on Windows only) + and change the default ``user_data_dir`` behaviour to use a *non*-roaming + profile dir (``CSIDL_LOCAL_APPDATA`` instead of ``CSIDL_APPDATA``). Why? Because + a large roaming profile can cause login speed issues. The "only syncs on + logout" behaviour can cause surprises in appdata info. + + +appdirs 1.0.1 (never released) +------------------------------ + +Started this changelog 27 July 2010. Before that this module originated in the +`Komodo `_ product as ``applib.py`` and then +as `applib/location.py +`_ (used by +`PyPM `_ in `ActivePython +`_). This is basically a fork of +applib.py 1.0.1 and applib/location.py 1.0.1. + + + diff --git a/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/RECORD b/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..3dd51b716fb8daf902d90c57e81542daed09f08f --- /dev/null +++ b/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/RECORD @@ -0,0 +1,11 @@ +appdirs.py,sha256=MievUEuv3l_mQISH5SF0shDk_BNhHHzYiAPrT3ITN4I,24701 +appdirs-1.4.3.dist-info/AUTHORS.txt,sha256=RtqU9KfonVGhI48DAA4-yTOBUhBtQTjFhaDzHoyh7uU,21518 +appdirs-1.4.3.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090 +appdirs-1.4.3.dist-info/METADATA,sha256=CYv_MUT9ndNhXtkeyXnWjTM6jW_z6fsu8wqD6Yxfn0k,8831 +appdirs-1.4.3.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +appdirs-1.4.3.dist-info/top_level.txt,sha256=nKncE8CUqZERJ6VuQWL4_bkunSPDNfn7KZqb4Tr5YEM,8 +appdirs-1.4.3.dist-info/RECORD,, +appdirs-1.4.3.dist-info/INSTALLER,, +appdirs-1.4.3.dist-info/__pycache__,, +appdirs.cpython-38.pyc,, +appdirs-1.4.3.virtualenv,, \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/WHEEL b/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ef99c6cf3283b50a273ac4c6d009a0aa85597070 --- /dev/null +++ b/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..d64bc321a11c17064f6e048ffcee9044688aa819 --- /dev/null +++ b/robot/lib/python3.8/site-packages/appdirs-1.4.3.dist-info/top_level.txt @@ -0,0 +1 @@ +appdirs diff --git a/robot/lib/python3.8/site-packages/appdirs-1.4.3.virtualenv b/robot/lib/python3.8/site-packages/appdirs-1.4.3.virtualenv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/appdirs.py b/robot/lib/python3.8/site-packages/appdirs.py new file mode 100644 index 0000000000000000000000000000000000000000..ae67001af8b661373edeee2eb327b9f63e630d62 --- /dev/null +++ b/robot/lib/python3.8/site-packages/appdirs.py @@ -0,0 +1,608 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2005-2010 ActiveState Software Inc. +# Copyright (c) 2013 Eddy Petrișor + +"""Utilities for determining application-specific dirs. + +See for details and usage. +""" +# Dev Notes: +# - MSDN on where to store app data files: +# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 +# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html +# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + +__version_info__ = (1, 4, 3) +__version__ = '.'.join(map(str, __version_info__)) + + +import sys +import os + +PY3 = sys.version_info[0] == 3 + +if PY3: + unicode = str + +if sys.platform.startswith('java'): + import platform + os_name = platform.java_ver()[3][0] + if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc. + system = 'win32' + elif os_name.startswith('Mac'): # "Mac OS X", etc. + system = 'darwin' + else: # "Linux", "SunOS", "FreeBSD", etc. + # Setting this to "linux2" is not ideal, but only Windows or Mac + # are actually checked for and the rest of the module expects + # *sys.platform* style strings. + system = 'linux2' +else: + system = sys.platform + + + +def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user data directories are: + Mac OS X: ~/Library/Application Support/ + Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined + Win XP (not roaming): C:\Documents and Settings\\Application Data\\ + Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ + Win 7 (not roaming): C:\Users\\AppData\Local\\ + Win 7 (roaming): C:\Users\\AppData\Roaming\\ + + For Unix, we follow the XDG spec and support $XDG_DATA_HOME. + That means, by default "~/.local/share/". + """ + if system == "win32": + if appauthor is None: + appauthor = appname + const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" + path = os.path.normpath(_get_win_folder(const)) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('~/Library/Application Support/') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of data dirs should be + returned. By default, the first item from XDG_DATA_DIRS is + returned, or '/usr/local/share/', + if XDG_DATA_DIRS is not set + + Typical site data directories are: + Mac OS X: /Library/Application Support/ + Unix: /usr/local/share/ or /usr/share/ + Win XP: C:\Documents and Settings\All Users\Application Data\\ + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. + + For Unix, this is using the $XDG_DATA_DIRS[0] default. + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('/Library/Application Support') + if appname: + path = os.path.join(path, appname) + else: + # XDG default for $XDG_DATA_DIRS + # only first, if multipath is False + path = os.getenv('XDG_DATA_DIRS', + os.pathsep.join(['/usr/local/share', '/usr/share'])) + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + if appname and version: + path = os.path.join(path, version) + return path + + +def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific config dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user config directories are: + Mac OS X: same as user_data_dir + Unix: ~/.config/ # or in $XDG_CONFIG_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. + That means, by default "~/.config/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of config dirs should be + returned. By default, the first item from XDG_CONFIG_DIRS is + returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set + + Typical site config directories are: + Mac OS X: same as site_data_dir + Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in + $XDG_CONFIG_DIRS + Win *: same as site_data_dir + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + + For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system in ["win32", "darwin"]: + path = site_data_dir(appname, appauthor) + if appname and version: + path = os.path.join(path, version) + else: + # XDG default for $XDG_CONFIG_DIRS + # only first, if multipath is False + path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + +def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific cache dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Cache" to the base app data dir for Windows. See + discussion below. + + Typical user cache directories are: + Mac OS X: ~/Library/Caches/ + Unix: ~/.cache/ (XDG default) + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache + Vista: C:\Users\\AppData\Local\\\Cache + + On Windows the only suggestion in the MSDN docs is that local settings go in + the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming + app data dir (the default returned by `user_data_dir` above). Apps typically + put cache data somewhere *under* the given dir here. Some examples: + ...\Mozilla\Firefox\Profiles\\Cache + ...\Acme\SuperApp\Cache\1.0 + OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. + This can be disabled with the `opinion=False` option. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + if opinion: + path = os.path.join(path, "Cache") + elif system == 'darwin': + path = os.path.expanduser('~/Library/Caches') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific state dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user state directories are: + Mac OS X: same as user_data_dir + Unix: ~/.local/state/ # or in $XDG_STATE_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow this Debian proposal + to extend the XDG spec and support $XDG_STATE_HOME. + + That means, by default "~/.local/state/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific log dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Logs" to the base app data dir for Windows, and "log" to the + base cache dir for Unix. See discussion below. + + Typical user log directories are: + Mac OS X: ~/Library/Logs/ + Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs + Vista: C:\Users\\AppData\Local\\\Logs + + On Windows the only suggestion in the MSDN docs is that local settings + go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in + examples of what some windows apps use for a logs dir.) + + OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` + value for Windows and appends "log" to the user cache dir for Unix. + This can be disabled with the `opinion=False` option. + """ + if system == "darwin": + path = os.path.join( + os.path.expanduser('~/Library/Logs'), + appname) + elif system == "win32": + path = user_data_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "Logs") + else: + path = user_cache_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "log") + if appname and version: + path = os.path.join(path, version) + return path + + +class AppDirs(object): + """Convenience wrapper for getting application dirs.""" + def __init__(self, appname=None, appauthor=None, version=None, + roaming=False, multipath=False): + self.appname = appname + self.appauthor = appauthor + self.version = version + self.roaming = roaming + self.multipath = multipath + + @property + def user_data_dir(self): + return user_data_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_data_dir(self): + return site_data_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_config_dir(self): + return user_config_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_config_dir(self): + return site_config_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_cache_dir(self): + return user_cache_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_state_dir(self): + return user_state_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_log_dir(self): + return user_log_dir(self.appname, self.appauthor, + version=self.version) + + +#---- internal support stuff + +def _get_win_folder_from_registry(csidl_name): + """This is a fallback technique at best. I'm not sure if using the + registry for this guarantees us the correct answer for all CSIDL_* + names. + """ + if PY3: + import winreg as _winreg + else: + import _winreg + + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + }[csidl_name] + + key = _winreg.OpenKey( + _winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" + ) + dir, type = _winreg.QueryValueEx(key, shell_folder_name) + return dir + + +def _get_win_folder_with_pywin32(csidl_name): + from win32com.shell import shellcon, shell + dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) + # Try to make this a unicode path because SHGetFolderPath does + # not return unicode strings when there is unicode data in the + # path. + try: + dir = unicode(dir) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + try: + import win32api + dir = win32api.GetShortPathName(dir) + except ImportError: + pass + except UnicodeError: + pass + return dir + + +def _get_win_folder_with_ctypes(csidl_name): + import ctypes + + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + }[csidl_name] + + buf = ctypes.create_unicode_buffer(1024) + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in buf: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf2 = ctypes.create_unicode_buffer(1024) + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + return buf.value + +def _get_win_folder_with_jna(csidl_name): + import array + from com.sun import jna + from com.sun.jna.platform import win32 + + buf_size = win32.WinDef.MAX_PATH * 2 + buf = array.zeros('c', buf_size) + shell = win32.Shell32.INSTANCE + shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf) + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf = array.zeros('c', buf_size) + kernel = win32.Kernel32.INSTANCE + if kernel.GetShortPathName(dir, buf, buf_size): + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + return dir + +if system == "win32": + try: + import win32com.shell + _get_win_folder = _get_win_folder_with_pywin32 + except ImportError: + try: + from ctypes import windll + _get_win_folder = _get_win_folder_with_ctypes + except ImportError: + try: + import com.sun.jna + _get_win_folder = _get_win_folder_with_jna + except ImportError: + _get_win_folder = _get_win_folder_from_registry + + +#---- self test code + +if __name__ == "__main__": + appname = "MyApp" + appauthor = "MyCompany" + + props = ("user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "site_data_dir", + "site_config_dir") + + print("-- app dirs %s --" % __version__) + + print("-- app dirs (with optional 'version')") + dirs = AppDirs(appname, appauthor, version="1.0") + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'version')") + dirs = AppDirs(appname, appauthor) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'appauthor')") + dirs = AppDirs(appname) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (with disabled 'appauthor')") + dirs = AppDirs(appname, appauthor=False) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) diff --git a/robot/lib/python3.8/site-packages/attr/__init__.py b/robot/lib/python3.8/site-packages/attr/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bf329cad5c88580c3e1e89ed9196dd58bdf7fe3b --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/__init__.py @@ -0,0 +1,76 @@ +from __future__ import absolute_import, division, print_function + +import sys + +from functools import partial + +from . import converters, exceptions, filters, setters, validators +from ._config import get_run_validators, set_run_validators +from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types +from ._make import ( + NOTHING, + Attribute, + Factory, + attrib, + attrs, + fields, + fields_dict, + make_class, + validate, +) +from ._version_info import VersionInfo + + +__version__ = "20.3.0" +__version_info__ = VersionInfo._from_version_string(__version__) + +__title__ = "attrs" +__description__ = "Classes Without Boilerplate" +__url__ = "https://www.attrs.org/" +__uri__ = __url__ +__doc__ = __description__ + " <" + __uri__ + ">" + +__author__ = "Hynek Schlawack" +__email__ = "hs@ox.cx" + +__license__ = "MIT" +__copyright__ = "Copyright (c) 2015 Hynek Schlawack" + + +s = attributes = attrs +ib = attr = attrib +dataclass = partial(attrs, auto_attribs=True) # happy Easter ;) + +__all__ = [ + "Attribute", + "Factory", + "NOTHING", + "asdict", + "assoc", + "astuple", + "attr", + "attrib", + "attributes", + "attrs", + "converters", + "evolve", + "exceptions", + "fields", + "fields_dict", + "filters", + "get_run_validators", + "has", + "ib", + "make_class", + "resolve_types", + "s", + "set_run_validators", + "setters", + "validate", + "validators", +] + +if sys.version_info[:2] >= (3, 6): + from ._next_gen import define, field, frozen, mutable + + __all__.extend((define, field, frozen, mutable)) diff --git a/robot/lib/python3.8/site-packages/attr/__init__.pyi b/robot/lib/python3.8/site-packages/attr/__init__.pyi new file mode 100644 index 0000000000000000000000000000000000000000..442d6e77fb840c8bbe5a95339d43ada698cd4eea --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/__init__.pyi @@ -0,0 +1,433 @@ +from typing import ( + Any, + Callable, + Dict, + Generic, + List, + Optional, + Sequence, + Mapping, + Tuple, + Type, + TypeVar, + Union, + overload, +) + +# `import X as X` is required to make these public +from . import exceptions as exceptions +from . import filters as filters +from . import converters as converters +from . import setters as setters +from . import validators as validators + +from ._version_info import VersionInfo + +__version__: str +__version_info__: VersionInfo +__title__: str +__description__: str +__url__: str +__uri__: str +__author__: str +__email__: str +__license__: str +__copyright__: str + +_T = TypeVar("_T") +_C = TypeVar("_C", bound=type) + +_ValidatorType = Callable[[Any, Attribute[_T], _T], Any] +_ConverterType = Callable[[Any], Any] +_FilterType = Callable[[Attribute[_T], _T], bool] +_ReprType = Callable[[Any], str] +_ReprArgType = Union[bool, _ReprType] +_OnSetAttrType = Callable[[Any, Attribute[Any], Any], Any] +_OnSetAttrArgType = Union[ + _OnSetAttrType, List[_OnSetAttrType], setters._NoOpType +] +_FieldTransformer = Callable[[type, List[Attribute]], List[Attribute]] +# FIXME: in reality, if multiple validators are passed they must be in a list +# or tuple, but those are invariant and so would prevent subtypes of +# _ValidatorType from working when passed in a list or tuple. +_ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]] + +# _make -- + +NOTHING: object + +# NOTE: Factory lies about its return type to make this possible: +# `x: List[int] # = Factory(list)` +# Work around mypy issue #4554 in the common case by using an overload. +@overload +def Factory(factory: Callable[[], _T]) -> _T: ... +@overload +def Factory( + factory: Union[Callable[[Any], _T], Callable[[], _T]], + takes_self: bool = ..., +) -> _T: ... + +class Attribute(Generic[_T]): + name: str + default: Optional[_T] + validator: Optional[_ValidatorType[_T]] + repr: _ReprArgType + cmp: bool + eq: bool + order: bool + hash: Optional[bool] + init: bool + converter: Optional[_ConverterType] + metadata: Dict[Any, Any] + type: Optional[Type[_T]] + kw_only: bool + on_setattr: _OnSetAttrType + +# NOTE: We had several choices for the annotation to use for type arg: +# 1) Type[_T] +# - Pros: Handles simple cases correctly +# - Cons: Might produce less informative errors in the case of conflicting +# TypeVars e.g. `attr.ib(default='bad', type=int)` +# 2) Callable[..., _T] +# - Pros: Better error messages than #1 for conflicting TypeVars +# - Cons: Terrible error messages for validator checks. +# e.g. attr.ib(type=int, validator=validate_str) +# -> error: Cannot infer function type argument +# 3) type (and do all of the work in the mypy plugin) +# - Pros: Simple here, and we could customize the plugin with our own errors. +# - Cons: Would need to write mypy plugin code to handle all the cases. +# We chose option #1. + +# `attr` lies about its return type to make the following possible: +# attr() -> Any +# attr(8) -> int +# attr(validator=) -> Whatever the callable expects. +# This makes this type of assignments possible: +# x: int = attr(8) +# +# This form catches explicit None or no default but with no other arguments +# returns Any. +@overload +def attrib( + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + cmp: Optional[bool] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: None = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def attrib( + default: None = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + cmp: Optional[bool] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: Optional[Type[_T]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def attrib( + default: _T, + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + cmp: Optional[bool] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: Optional[Type[_T]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def attrib( + default: Optional[_T] = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + cmp: Optional[bool] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: object = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... +@overload +def field( + *, + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def field( + *, + default: None = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def field( + *, + default: _T, + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def field( + *, + default: Optional[_T] = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... +@overload +def attrs( + maybe_cls: _C, + these: Optional[Dict[str, Any]] = ..., + repr_ns: Optional[str] = ..., + repr: bool = ..., + cmp: Optional[bool] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., +) -> _C: ... +@overload +def attrs( + maybe_cls: None = ..., + these: Optional[Dict[str, Any]] = ..., + repr_ns: Optional[str] = ..., + repr: bool = ..., + cmp: Optional[bool] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., +) -> Callable[[_C], _C]: ... +@overload +def define( + maybe_cls: _C, + *, + these: Optional[Dict[str, Any]] = ..., + repr: bool = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., +) -> _C: ... +@overload +def define( + maybe_cls: None = ..., + *, + these: Optional[Dict[str, Any]] = ..., + repr: bool = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., +) -> Callable[[_C], _C]: ... + +mutable = define +frozen = define # they differ only in their defaults + +# TODO: add support for returning NamedTuple from the mypy plugin +class _Fields(Tuple[Attribute[Any], ...]): + def __getattr__(self, name: str) -> Attribute[Any]: ... + +def fields(cls: type) -> _Fields: ... +def fields_dict(cls: type) -> Dict[str, Attribute[Any]]: ... +def validate(inst: Any) -> None: ... +def resolve_types( + cls: _C, + globalns: Optional[Dict[str, Any]] = ..., + localns: Optional[Dict[str, Any]] = ..., +) -> _C: ... + +# TODO: add support for returning a proper attrs class from the mypy plugin +# we use Any instead of _CountingAttr so that e.g. `make_class('Foo', +# [attr.ib()])` is valid +def make_class( + name: str, + attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]], + bases: Tuple[type, ...] = ..., + repr_ns: Optional[str] = ..., + repr: bool = ..., + cmp: Optional[bool] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + collect_by_mro: bool = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., +) -> type: ... + +# _funcs -- + +# TODO: add support for returning TypedDict from the mypy plugin +# FIXME: asdict/astuple do not honor their factory args. Waiting on one of +# these: +# https://github.com/python/mypy/issues/4236 +# https://github.com/python/typing/issues/253 +def asdict( + inst: Any, + recurse: bool = ..., + filter: Optional[_FilterType[Any]] = ..., + dict_factory: Type[Mapping[Any, Any]] = ..., + retain_collection_types: bool = ..., + value_serializer: Optional[Callable[[type, Attribute, Any], Any]] = ..., +) -> Dict[str, Any]: ... + +# TODO: add support for returning NamedTuple from the mypy plugin +def astuple( + inst: Any, + recurse: bool = ..., + filter: Optional[_FilterType[Any]] = ..., + tuple_factory: Type[Sequence[Any]] = ..., + retain_collection_types: bool = ..., +) -> Tuple[Any, ...]: ... +def has(cls: type) -> bool: ... +def assoc(inst: _T, **changes: Any) -> _T: ... +def evolve(inst: _T, **changes: Any) -> _T: ... + +# _config -- + +def set_run_validators(run: bool) -> None: ... +def get_run_validators() -> bool: ... + +# aliases -- + +s = attributes = attrs +ib = attr = attrib +dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;) diff --git a/robot/lib/python3.8/site-packages/attr/_compat.py b/robot/lib/python3.8/site-packages/attr/_compat.py new file mode 100644 index 0000000000000000000000000000000000000000..b0ead6e1c8d269f3af46910a1f9671d0f7f5b98a --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/_compat.py @@ -0,0 +1,231 @@ +from __future__ import absolute_import, division, print_function + +import platform +import sys +import types +import warnings + + +PY2 = sys.version_info[0] == 2 +PYPY = platform.python_implementation() == "PyPy" + + +if PYPY or sys.version_info[:2] >= (3, 6): + ordered_dict = dict +else: + from collections import OrderedDict + + ordered_dict = OrderedDict + + +if PY2: + from collections import Mapping, Sequence + + from UserDict import IterableUserDict + + # We 'bundle' isclass instead of using inspect as importing inspect is + # fairly expensive (order of 10-15 ms for a modern machine in 2016) + def isclass(klass): + return isinstance(klass, (type, types.ClassType)) + + # TYPE is used in exceptions, repr(int) is different on Python 2 and 3. + TYPE = "type" + + def iteritems(d): + return d.iteritems() + + # Python 2 is bereft of a read-only dict proxy, so we make one! + class ReadOnlyDict(IterableUserDict): + """ + Best-effort read-only dict wrapper. + """ + + def __setitem__(self, key, val): + # We gently pretend we're a Python 3 mappingproxy. + raise TypeError( + "'mappingproxy' object does not support item assignment" + ) + + def update(self, _): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError( + "'mappingproxy' object has no attribute 'update'" + ) + + def __delitem__(self, _): + # We gently pretend we're a Python 3 mappingproxy. + raise TypeError( + "'mappingproxy' object does not support item deletion" + ) + + def clear(self): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError( + "'mappingproxy' object has no attribute 'clear'" + ) + + def pop(self, key, default=None): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError( + "'mappingproxy' object has no attribute 'pop'" + ) + + def popitem(self): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError( + "'mappingproxy' object has no attribute 'popitem'" + ) + + def setdefault(self, key, default=None): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError( + "'mappingproxy' object has no attribute 'setdefault'" + ) + + def __repr__(self): + # Override to be identical to the Python 3 version. + return "mappingproxy(" + repr(self.data) + ")" + + def metadata_proxy(d): + res = ReadOnlyDict() + res.data.update(d) # We blocked update, so we have to do it like this. + return res + + def just_warn(*args, **kw): # pragma: no cover + """ + We only warn on Python 3 because we are not aware of any concrete + consequences of not setting the cell on Python 2. + """ + + +else: # Python 3 and later. + from collections.abc import Mapping, Sequence # noqa + + def just_warn(*args, **kw): + """ + We only warn on Python 3 because we are not aware of any concrete + consequences of not setting the cell on Python 2. + """ + warnings.warn( + "Running interpreter doesn't sufficiently support code object " + "introspection. Some features like bare super() or accessing " + "__class__ will not work with slotted classes.", + RuntimeWarning, + stacklevel=2, + ) + + def isclass(klass): + return isinstance(klass, type) + + TYPE = "class" + + def iteritems(d): + return d.items() + + def metadata_proxy(d): + return types.MappingProxyType(dict(d)) + + +def make_set_closure_cell(): + """Return a function of two arguments (cell, value) which sets + the value stored in the closure cell `cell` to `value`. + """ + # pypy makes this easy. (It also supports the logic below, but + # why not do the easy/fast thing?) + if PYPY: + + def set_closure_cell(cell, value): + cell.__setstate__((value,)) + + return set_closure_cell + + # Otherwise gotta do it the hard way. + + # Create a function that will set its first cellvar to `value`. + def set_first_cellvar_to(value): + x = value + return + + # This function will be eliminated as dead code, but + # not before its reference to `x` forces `x` to be + # represented as a closure cell rather than a local. + def force_x_to_be_a_cell(): # pragma: no cover + return x + + try: + # Extract the code object and make sure our assumptions about + # the closure behavior are correct. + if PY2: + co = set_first_cellvar_to.func_code + else: + co = set_first_cellvar_to.__code__ + if co.co_cellvars != ("x",) or co.co_freevars != (): + raise AssertionError # pragma: no cover + + # Convert this code object to a code object that sets the + # function's first _freevar_ (not cellvar) to the argument. + if sys.version_info >= (3, 8): + # CPython 3.8+ has an incompatible CodeType signature + # (added a posonlyargcount argument) but also added + # CodeType.replace() to do this without counting parameters. + set_first_freevar_code = co.replace( + co_cellvars=co.co_freevars, co_freevars=co.co_cellvars + ) + else: + args = [co.co_argcount] + if not PY2: + args.append(co.co_kwonlyargcount) + args.extend( + [ + co.co_nlocals, + co.co_stacksize, + co.co_flags, + co.co_code, + co.co_consts, + co.co_names, + co.co_varnames, + co.co_filename, + co.co_name, + co.co_firstlineno, + co.co_lnotab, + # These two arguments are reversed: + co.co_cellvars, + co.co_freevars, + ] + ) + set_first_freevar_code = types.CodeType(*args) + + def set_closure_cell(cell, value): + # Create a function using the set_first_freevar_code, + # whose first closure cell is `cell`. Calling it will + # change the value of that cell. + setter = types.FunctionType( + set_first_freevar_code, {}, "setter", (), (cell,) + ) + # And call it to set the cell. + setter(value) + + # Make sure it works on this interpreter: + def make_func_with_cell(): + x = None + + def func(): + return x # pragma: no cover + + return func + + if PY2: + cell = make_func_with_cell().func_closure[0] + else: + cell = make_func_with_cell().__closure__[0] + set_closure_cell(cell, 100) + if cell.cell_contents != 100: + raise AssertionError # pragma: no cover + + except Exception: + return just_warn + else: + return set_closure_cell + + +set_closure_cell = make_set_closure_cell() diff --git a/robot/lib/python3.8/site-packages/attr/_config.py b/robot/lib/python3.8/site-packages/attr/_config.py new file mode 100644 index 0000000000000000000000000000000000000000..8ec920962d1b401335927db837cc13cca1e99ae8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/_config.py @@ -0,0 +1,23 @@ +from __future__ import absolute_import, division, print_function + + +__all__ = ["set_run_validators", "get_run_validators"] + +_run_validators = True + + +def set_run_validators(run): + """ + Set whether or not validators are run. By default, they are run. + """ + if not isinstance(run, bool): + raise TypeError("'run' must be bool.") + global _run_validators + _run_validators = run + + +def get_run_validators(): + """ + Return whether or not validators are run. + """ + return _run_validators diff --git a/robot/lib/python3.8/site-packages/attr/_funcs.py b/robot/lib/python3.8/site-packages/attr/_funcs.py new file mode 100644 index 0000000000000000000000000000000000000000..e6c930cbd1711caa63014eae133439ad25f32272 --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/_funcs.py @@ -0,0 +1,390 @@ +from __future__ import absolute_import, division, print_function + +import copy + +from ._compat import iteritems +from ._make import NOTHING, _obj_setattr, fields +from .exceptions import AttrsAttributeNotFoundError + + +def asdict( + inst, + recurse=True, + filter=None, + dict_factory=dict, + retain_collection_types=False, + value_serializer=None, +): + """ + Return the ``attrs`` attribute values of *inst* as a dict. + + Optionally recurse into other ``attrs``-decorated classes. + + :param inst: Instance of an ``attrs``-decorated class. + :param bool recurse: Recurse into classes that are also + ``attrs``-decorated. + :param callable filter: A callable whose return code determines whether an + attribute or element is included (``True``) or dropped (``False``). Is + called with the `attr.Attribute` as the first argument and the + value as the second argument. + :param callable dict_factory: A callable to produce dictionaries from. For + example, to produce ordered dictionaries instead of normal Python + dictionaries, pass in ``collections.OrderedDict``. + :param bool retain_collection_types: Do not convert to ``list`` when + encountering an attribute whose type is ``tuple`` or ``set``. Only + meaningful if ``recurse`` is ``True``. + :param Optional[callable] value_serializer: A hook that is called for every + attribute or dict key/value. It receives the current instance, field + and value and must return the (updated) value. The hook is run *after* + the optional *filter* has been applied. + + :rtype: return type of *dict_factory* + + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 16.0.0 *dict_factory* + .. versionadded:: 16.1.0 *retain_collection_types* + .. versionadded:: 20.3.0 *value_serializer* + """ + attrs = fields(inst.__class__) + rv = dict_factory() + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + + if value_serializer is not None: + v = value_serializer(inst, a, v) + + if recurse is True: + if has(v.__class__): + rv[a.name] = asdict( + v, + True, + filter, + dict_factory, + retain_collection_types, + value_serializer, + ) + elif isinstance(v, (tuple, list, set, frozenset)): + cf = v.__class__ if retain_collection_types is True else list + rv[a.name] = cf( + [ + _asdict_anything( + i, + filter, + dict_factory, + retain_collection_types, + value_serializer, + ) + for i in v + ] + ) + elif isinstance(v, dict): + df = dict_factory + rv[a.name] = df( + ( + _asdict_anything( + kk, + filter, + df, + retain_collection_types, + value_serializer, + ), + _asdict_anything( + vv, + filter, + df, + retain_collection_types, + value_serializer, + ), + ) + for kk, vv in iteritems(v) + ) + else: + rv[a.name] = v + else: + rv[a.name] = v + return rv + + +def _asdict_anything( + val, + filter, + dict_factory, + retain_collection_types, + value_serializer, +): + """ + ``asdict`` only works on attrs instances, this works on anything. + """ + if getattr(val.__class__, "__attrs_attrs__", None) is not None: + # Attrs class. + rv = asdict( + val, + True, + filter, + dict_factory, + retain_collection_types, + value_serializer, + ) + elif isinstance(val, (tuple, list, set, frozenset)): + cf = val.__class__ if retain_collection_types is True else list + rv = cf( + [ + _asdict_anything( + i, + filter, + dict_factory, + retain_collection_types, + value_serializer, + ) + for i in val + ] + ) + elif isinstance(val, dict): + df = dict_factory + rv = df( + ( + _asdict_anything( + kk, filter, df, retain_collection_types, value_serializer + ), + _asdict_anything( + vv, filter, df, retain_collection_types, value_serializer + ), + ) + for kk, vv in iteritems(val) + ) + else: + rv = val + if value_serializer is not None: + rv = value_serializer(None, None, rv) + + return rv + + +def astuple( + inst, + recurse=True, + filter=None, + tuple_factory=tuple, + retain_collection_types=False, +): + """ + Return the ``attrs`` attribute values of *inst* as a tuple. + + Optionally recurse into other ``attrs``-decorated classes. + + :param inst: Instance of an ``attrs``-decorated class. + :param bool recurse: Recurse into classes that are also + ``attrs``-decorated. + :param callable filter: A callable whose return code determines whether an + attribute or element is included (``True``) or dropped (``False``). Is + called with the `attr.Attribute` as the first argument and the + value as the second argument. + :param callable tuple_factory: A callable to produce tuples from. For + example, to produce lists instead of tuples. + :param bool retain_collection_types: Do not convert to ``list`` + or ``dict`` when encountering an attribute which type is + ``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is + ``True``. + + :rtype: return type of *tuple_factory* + + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 16.2.0 + """ + attrs = fields(inst.__class__) + rv = [] + retain = retain_collection_types # Very long. :/ + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + if recurse is True: + if has(v.__class__): + rv.append( + astuple( + v, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + ) + elif isinstance(v, (tuple, list, set, frozenset)): + cf = v.__class__ if retain is True else list + rv.append( + cf( + [ + astuple( + j, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(j.__class__) + else j + for j in v + ] + ) + ) + elif isinstance(v, dict): + df = v.__class__ if retain is True else dict + rv.append( + df( + ( + astuple( + kk, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(kk.__class__) + else kk, + astuple( + vv, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(vv.__class__) + else vv, + ) + for kk, vv in iteritems(v) + ) + ) + else: + rv.append(v) + else: + rv.append(v) + + return rv if tuple_factory is list else tuple_factory(rv) + + +def has(cls): + """ + Check whether *cls* is a class with ``attrs`` attributes. + + :param type cls: Class to introspect. + :raise TypeError: If *cls* is not a class. + + :rtype: bool + """ + return getattr(cls, "__attrs_attrs__", None) is not None + + +def assoc(inst, **changes): + """ + Copy *inst* and apply *changes*. + + :param inst: Instance of a class with ``attrs`` attributes. + :param changes: Keyword changes in the new copy. + + :return: A copy of inst with *changes* incorporated. + + :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't + be found on *cls*. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. deprecated:: 17.1.0 + Use `evolve` instead. + """ + import warnings + + warnings.warn( + "assoc is deprecated and will be removed after 2018/01.", + DeprecationWarning, + stacklevel=2, + ) + new = copy.copy(inst) + attrs = fields(inst.__class__) + for k, v in iteritems(changes): + a = getattr(attrs, k, NOTHING) + if a is NOTHING: + raise AttrsAttributeNotFoundError( + "{k} is not an attrs attribute on {cl}.".format( + k=k, cl=new.__class__ + ) + ) + _obj_setattr(new, k, v) + return new + + +def evolve(inst, **changes): + """ + Create a new instance, based on *inst* with *changes* applied. + + :param inst: Instance of a class with ``attrs`` attributes. + :param changes: Keyword changes in the new copy. + + :return: A copy of inst with *changes* incorporated. + + :raise TypeError: If *attr_name* couldn't be found in the class + ``__init__``. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 17.1.0 + """ + cls = inst.__class__ + attrs = fields(cls) + for a in attrs: + if not a.init: + continue + attr_name = a.name # To deal with private attributes. + init_name = attr_name if attr_name[0] != "_" else attr_name[1:] + if init_name not in changes: + changes[init_name] = getattr(inst, attr_name) + + return cls(**changes) + + +def resolve_types(cls, globalns=None, localns=None): + """ + Resolve any strings and forward annotations in type annotations. + + This is only required if you need concrete types in `Attribute`'s *type* + field. In other words, you don't need to resolve your types if you only + use them for static type checking. + + With no arguments, names will be looked up in the module in which the class + was created. If this is not what you want, e.g. if the name only exists + inside a method, you may pass *globalns* or *localns* to specify other + dictionaries in which to look up these names. See the docs of + `typing.get_type_hints` for more details. + + :param type cls: Class to resolve. + :param Optional[dict] globalns: Dictionary containing global variables. + :param Optional[dict] localns: Dictionary containing local variables. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + :raise NameError: If types cannot be resolved because of missing variables. + + :returns: *cls* so you can use this function also as a class decorator. + Please note that you have to apply it **after** `attr.s`. That means + the decorator has to come in the line **before** `attr.s`. + + .. versionadded:: 20.1.0 + """ + try: + # Since calling get_type_hints is expensive we cache whether we've + # done it already. + cls.__attrs_types_resolved__ + except AttributeError: + import typing + + hints = typing.get_type_hints(cls, globalns=globalns, localns=localns) + for field in fields(cls): + if field.name in hints: + # Since fields have been frozen we must work around it. + _obj_setattr(field, "type", hints[field.name]) + cls.__attrs_types_resolved__ = True + + # Return the class so you can use it as a decorator too. + return cls diff --git a/robot/lib/python3.8/site-packages/attr/_make.py b/robot/lib/python3.8/site-packages/attr/_make.py new file mode 100644 index 0000000000000000000000000000000000000000..49484f935f6929e2d070cc1149ecdd70c4f6a83e --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/_make.py @@ -0,0 +1,2765 @@ +from __future__ import absolute_import, division, print_function + +import copy +import linecache +import sys +import threading +import uuid +import warnings + +from operator import itemgetter + +from . import _config, setters +from ._compat import ( + PY2, + PYPY, + isclass, + iteritems, + metadata_proxy, + ordered_dict, + set_closure_cell, +) +from .exceptions import ( + DefaultAlreadySetError, + FrozenInstanceError, + NotAnAttrsClassError, + PythonTooOldError, + UnannotatedAttributeError, +) + + +# This is used at least twice, so cache it here. +_obj_setattr = object.__setattr__ +_init_converter_pat = "__attr_converter_%s" +_init_factory_pat = "__attr_factory_{}" +_tuple_property_pat = ( + " {attr_name} = _attrs_property(_attrs_itemgetter({index}))" +) +_classvar_prefixes = ("typing.ClassVar", "t.ClassVar", "ClassVar") +# we don't use a double-underscore prefix because that triggers +# name mangling when trying to create a slot for the field +# (when slots=True) +_hash_cache_field = "_attrs_cached_hash" + +_empty_metadata_singleton = metadata_proxy({}) + +# Unique object for unequivocal getattr() defaults. +_sentinel = object() + + +class _Nothing(object): + """ + Sentinel class to indicate the lack of a value when ``None`` is ambiguous. + + ``_Nothing`` is a singleton. There is only ever one of it. + """ + + _singleton = None + + def __new__(cls): + if _Nothing._singleton is None: + _Nothing._singleton = super(_Nothing, cls).__new__(cls) + return _Nothing._singleton + + def __repr__(self): + return "NOTHING" + + +NOTHING = _Nothing() +""" +Sentinel to indicate the lack of a value when ``None`` is ambiguous. +""" + + +class _CacheHashWrapper(int): + """ + An integer subclass that pickles / copies as None + + This is used for non-slots classes with ``cache_hash=True``, to avoid + serializing a potentially (even likely) invalid hash value. Since ``None`` + is the default value for uncalculated hashes, whenever this is copied, + the copy's value for the hash should automatically reset. + + See GH #613 for more details. + """ + + if PY2: + # For some reason `type(None)` isn't callable in Python 2, but we don't + # actually need a constructor for None objects, we just need any + # available function that returns None. + def __reduce__(self, _none_constructor=getattr, _args=(0, "", None)): + return _none_constructor, _args + + else: + + def __reduce__(self, _none_constructor=type(None), _args=()): + return _none_constructor, _args + + +def attrib( + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=None, + init=True, + metadata=None, + type=None, + converter=None, + factory=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, +): + """ + Create a new attribute on a class. + + .. warning:: + + Does *not* do anything unless the class is also decorated with + `attr.s`! + + :param default: A value that is used if an ``attrs``-generated ``__init__`` + is used and no value is passed while instantiating or the attribute is + excluded using ``init=False``. + + If the value is an instance of `Factory`, its callable will be + used to construct a new value (useful for mutable data types like lists + or dicts). + + If a default is not set (or set manually to `attr.NOTHING`), a value + *must* be supplied when instantiating; otherwise a `TypeError` + will be raised. + + The default can also be set using decorator notation as shown below. + + :type default: Any value + + :param callable factory: Syntactic sugar for + ``default=attr.Factory(factory)``. + + :param validator: `callable` that is called by ``attrs``-generated + ``__init__`` methods after the instance has been initialized. They + receive the initialized instance, the `Attribute`, and the + passed value. + + The return value is *not* inspected so the validator has to throw an + exception itself. + + If a `list` is passed, its items are treated as validators and must + all pass. + + Validators can be globally disabled and re-enabled using + `get_run_validators`. + + The validator can also be set using decorator notation as shown below. + + :type validator: `callable` or a `list` of `callable`\\ s. + + :param repr: Include this attribute in the generated ``__repr__`` + method. If ``True``, include the attribute; if ``False``, omit it. By + default, the built-in ``repr()`` function is used. To override how the + attribute value is formatted, pass a ``callable`` that takes a single + value and returns a string. Note that the resulting string is used + as-is, i.e. it will be used directly *instead* of calling ``repr()`` + (the default). + :type repr: a `bool` or a `callable` to use a custom function. + :param bool eq: If ``True`` (default), include this attribute in the + generated ``__eq__`` and ``__ne__`` methods that check two instances + for equality. + :param bool order: If ``True`` (default), include this attributes in the + generated ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. + :param bool cmp: Setting to ``True`` is equivalent to setting ``eq=True, + order=True``. Deprecated in favor of *eq* and *order*. + :param Optional[bool] hash: Include this attribute in the generated + ``__hash__`` method. If ``None`` (default), mirror *eq*'s value. This + is the correct behavior according the Python spec. Setting this value + to anything else than ``None`` is *discouraged*. + :param bool init: Include this attribute in the generated ``__init__`` + method. It is possible to set this to ``False`` and set a default + value. In that case this attributed is unconditionally initialized + with the specified default value or factory. + :param callable converter: `callable` that is called by + ``attrs``-generated ``__init__`` methods to convert attribute's value + to the desired format. It is given the passed-in value, and the + returned value will be used as the new value of the attribute. The + value is converted before being passed to the validator, if any. + :param metadata: An arbitrary mapping, to be used by third-party + components. See `extending_metadata`. + :param type: The type of the attribute. In Python 3.6 or greater, the + preferred method to specify the type is using a variable annotation + (see `PEP 526 `_). + This argument is provided for backward compatibility. + Regardless of the approach used, the type will be stored on + ``Attribute.type``. + + Please note that ``attrs`` doesn't do anything with this metadata by + itself. You can use it as part of your own code or for + `static type checking `. + :param kw_only: Make this attribute keyword-only (Python 3+) + in the generated ``__init__`` (if ``init`` is ``False``, this + parameter is ignored). + :param on_setattr: Allows to overwrite the *on_setattr* setting from + `attr.s`. If left `None`, the *on_setattr* value from `attr.s` is used. + Set to `attr.setters.NO_OP` to run **no** `setattr` hooks for this + attribute -- regardless of the setting in `attr.s`. + :type on_setattr: `callable`, or a list of callables, or `None`, or + `attr.setters.NO_OP` + + .. versionadded:: 15.2.0 *convert* + .. versionadded:: 16.3.0 *metadata* + .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. + .. versionchanged:: 17.1.0 + *hash* is ``None`` and therefore mirrors *eq* by default. + .. versionadded:: 17.3.0 *type* + .. deprecated:: 17.4.0 *convert* + .. versionadded:: 17.4.0 *converter* as a replacement for the deprecated + *convert* to achieve consistency with other noun-based arguments. + .. versionadded:: 18.1.0 + ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. + .. versionadded:: 18.2.0 *kw_only* + .. versionchanged:: 19.2.0 *convert* keyword argument removed + .. versionchanged:: 19.2.0 *repr* also accepts a custom callable. + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.3.0 *kw_only* backported to Python 2 + """ + eq, order = _determine_eq_order(cmp, eq, order, True) + + if hash is not None and hash is not True and hash is not False: + raise TypeError( + "Invalid value for hash. Must be True, False, or None." + ) + + if factory is not None: + if default is not NOTHING: + raise ValueError( + "The `default` and `factory` arguments are mutually " + "exclusive." + ) + if not callable(factory): + raise ValueError("The `factory` argument must be a callable.") + default = Factory(factory) + + if metadata is None: + metadata = {} + + # Apply syntactic sugar by auto-wrapping. + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + if validator and isinstance(validator, (list, tuple)): + validator = and_(*validator) + + if converter and isinstance(converter, (list, tuple)): + converter = pipe(*converter) + + return _CountingAttr( + default=default, + validator=validator, + repr=repr, + cmp=None, + hash=hash, + init=init, + converter=converter, + metadata=metadata, + type=type, + kw_only=kw_only, + eq=eq, + order=order, + on_setattr=on_setattr, + ) + + +def _make_attr_tuple_class(cls_name, attr_names): + """ + Create a tuple subclass to hold `Attribute`s for an `attrs` class. + + The subclass is a bare tuple with properties for names. + + class MyClassAttributes(tuple): + __slots__ = () + x = property(itemgetter(0)) + """ + attr_class_name = "{}Attributes".format(cls_name) + attr_class_template = [ + "class {}(tuple):".format(attr_class_name), + " __slots__ = ()", + ] + if attr_names: + for i, attr_name in enumerate(attr_names): + attr_class_template.append( + _tuple_property_pat.format(index=i, attr_name=attr_name) + ) + else: + attr_class_template.append(" pass") + globs = {"_attrs_itemgetter": itemgetter, "_attrs_property": property} + eval(compile("\n".join(attr_class_template), "", "exec"), globs) + + return globs[attr_class_name] + + +# Tuple class for extracted attributes from a class definition. +# `base_attrs` is a subset of `attrs`. +_Attributes = _make_attr_tuple_class( + "_Attributes", + [ + # all attributes to build dunder methods for + "attrs", + # attributes that have been inherited + "base_attrs", + # map inherited attributes to their originating classes + "base_attrs_map", + ], +) + + +def _is_class_var(annot): + """ + Check whether *annot* is a typing.ClassVar. + + The string comparison hack is used to avoid evaluating all string + annotations which would put attrs-based classes at a performance + disadvantage compared to plain old classes. + """ + return str(annot).startswith(_classvar_prefixes) + + +def _has_own_attribute(cls, attrib_name): + """ + Check whether *cls* defines *attrib_name* (and doesn't just inherit it). + + Requires Python 3. + """ + attr = getattr(cls, attrib_name, _sentinel) + if attr is _sentinel: + return False + + for base_cls in cls.__mro__[1:]: + a = getattr(base_cls, attrib_name, None) + if attr is a: + return False + + return True + + +def _get_annotations(cls): + """ + Get annotations for *cls*. + """ + if _has_own_attribute(cls, "__annotations__"): + return cls.__annotations__ + + return {} + + +def _counter_getter(e): + """ + Key function for sorting to avoid re-creating a lambda for every class. + """ + return e[1].counter + + +def _collect_base_attrs(cls, taken_attr_names): + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in reversed(cls.__mro__[1:-1]): + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.inherited or a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + # For each name, only keep the freshest definition i.e. the furthest at the + # back. base_attr_map is fine because it gets overwritten with every new + # instance. + filtered = [] + seen = set() + for a in reversed(base_attrs): + if a.name in seen: + continue + filtered.insert(0, a) + seen.add(a.name) + + return filtered, base_attr_map + + +def _collect_base_attrs_broken(cls, taken_attr_names): + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + + N.B. *taken_attr_names* will be mutated. + + Adhere to the old incorrect behavior. + + Notably it collects from the front and considers inherited attributes which + leads to the buggy behavior reported in #428. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in cls.__mro__[1:-1]: + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) + taken_attr_names.add(a.name) + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + return base_attrs, base_attr_map + + +def _transform_attrs( + cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer +): + """ + Transform all `_CountingAttr`s on a class into `Attribute`s. + + If *these* is passed, use that and don't look for them on the class. + + *collect_by_mro* is True, collect them in the correct MRO order, otherwise + use the old -- incorrect -- order. See #428. + + Return an `_Attributes`. + """ + cd = cls.__dict__ + anns = _get_annotations(cls) + + if these is not None: + ca_list = [(name, ca) for name, ca in iteritems(these)] + + if not isinstance(these, ordered_dict): + ca_list.sort(key=_counter_getter) + elif auto_attribs is True: + ca_names = { + name + for name, attr in cd.items() + if isinstance(attr, _CountingAttr) + } + ca_list = [] + annot_names = set() + for attr_name, type in anns.items(): + if _is_class_var(type): + continue + annot_names.add(attr_name) + a = cd.get(attr_name, NOTHING) + + if not isinstance(a, _CountingAttr): + if a is NOTHING: + a = attrib() + else: + a = attrib(default=a) + ca_list.append((attr_name, a)) + + unannotated = ca_names - annot_names + if len(unannotated) > 0: + raise UnannotatedAttributeError( + "The following `attr.ib`s lack a type annotation: " + + ", ".join( + sorted(unannotated, key=lambda n: cd.get(n).counter) + ) + + "." + ) + else: + ca_list = sorted( + ( + (name, attr) + for name, attr in cd.items() + if isinstance(attr, _CountingAttr) + ), + key=lambda e: e[1].counter, + ) + + own_attrs = [ + Attribute.from_counting_attr( + name=attr_name, ca=ca, type=anns.get(attr_name) + ) + for attr_name, ca in ca_list + ] + + if collect_by_mro: + base_attrs, base_attr_map = _collect_base_attrs( + cls, {a.name for a in own_attrs} + ) + else: + base_attrs, base_attr_map = _collect_base_attrs_broken( + cls, {a.name for a in own_attrs} + ) + + attr_names = [a.name for a in base_attrs + own_attrs] + + AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) + + if kw_only: + own_attrs = [a.evolve(kw_only=True) for a in own_attrs] + base_attrs = [a.evolve(kw_only=True) for a in base_attrs] + + attrs = AttrsClass(base_attrs + own_attrs) + + # Mandatory vs non-mandatory attr order only matters when they are part of + # the __init__ signature and when they aren't kw_only (which are moved to + # the end and can be mandatory or non-mandatory in any order, as they will + # be specified as keyword args anyway). Check the order of those attrs: + had_default = False + for a in (a for a in attrs if a.init is not False and a.kw_only is False): + if had_default is True and a.default is NOTHING: + raise ValueError( + "No mandatory attributes allowed after an attribute with a " + "default value or factory. Attribute in question: %r" % (a,) + ) + + if had_default is False and a.default is not NOTHING: + had_default = True + + if field_transformer is not None: + attrs = field_transformer(cls, attrs) + return _Attributes((attrs, base_attrs, base_attr_map)) + + +if PYPY: + + def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + if isinstance(self, BaseException) and name in ( + "__cause__", + "__context__", + ): + BaseException.__setattr__(self, name, value) + return + + raise FrozenInstanceError() + + +else: + + def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + raise FrozenInstanceError() + + +def _frozen_delattrs(self, name): + """ + Attached to frozen classes as __delattr__. + """ + raise FrozenInstanceError() + + +class _ClassBuilder(object): + """ + Iteratively build *one* class. + """ + + __slots__ = ( + "_attr_names", + "_attrs", + "_base_attr_map", + "_base_names", + "_cache_hash", + "_cls", + "_cls_dict", + "_delete_attribs", + "_frozen", + "_has_post_init", + "_is_exc", + "_on_setattr", + "_slots", + "_weakref_slot", + "_has_own_setattr", + "_has_custom_setattr", + ) + + def __init__( + self, + cls, + these, + slots, + frozen, + weakref_slot, + getstate_setstate, + auto_attribs, + kw_only, + cache_hash, + is_exc, + collect_by_mro, + on_setattr, + has_custom_setattr, + field_transformer, + ): + attrs, base_attrs, base_map = _transform_attrs( + cls, + these, + auto_attribs, + kw_only, + collect_by_mro, + field_transformer, + ) + + self._cls = cls + self._cls_dict = dict(cls.__dict__) if slots else {} + self._attrs = attrs + self._base_names = set(a.name for a in base_attrs) + self._base_attr_map = base_map + self._attr_names = tuple(a.name for a in attrs) + self._slots = slots + self._frozen = frozen + self._weakref_slot = weakref_slot + self._cache_hash = cache_hash + self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) + self._delete_attribs = not bool(these) + self._is_exc = is_exc + self._on_setattr = on_setattr + + self._has_custom_setattr = has_custom_setattr + self._has_own_setattr = False + + self._cls_dict["__attrs_attrs__"] = self._attrs + + if frozen: + self._cls_dict["__setattr__"] = _frozen_setattrs + self._cls_dict["__delattr__"] = _frozen_delattrs + + self._has_own_setattr = True + + if getstate_setstate: + ( + self._cls_dict["__getstate__"], + self._cls_dict["__setstate__"], + ) = self._make_getstate_setstate() + + def __repr__(self): + return "<_ClassBuilder(cls={cls})>".format(cls=self._cls.__name__) + + def build_class(self): + """ + Finalize class based on the accumulated configuration. + + Builder cannot be used after calling this method. + """ + if self._slots is True: + return self._create_slots_class() + else: + return self._patch_original_class() + + def _patch_original_class(self): + """ + Apply accumulated methods and return the class. + """ + cls = self._cls + base_names = self._base_names + + # Clean class of attribute definitions (`attr.ib()`s). + if self._delete_attribs: + for name in self._attr_names: + if ( + name not in base_names + and getattr(cls, name, _sentinel) is not _sentinel + ): + try: + delattr(cls, name) + except AttributeError: + # This can happen if a base class defines a class + # variable and we want to set an attribute with the + # same name by using only a type annotation. + pass + + # Attach our dunder methods. + for name, value in self._cls_dict.items(): + setattr(cls, name, value) + + # If we've inherited an attrs __setattr__ and don't write our own, + # reset it to object's. + if not self._has_own_setattr and getattr( + cls, "__attrs_own_setattr__", False + ): + cls.__attrs_own_setattr__ = False + + if not self._has_custom_setattr: + cls.__setattr__ = object.__setattr__ + + return cls + + def _create_slots_class(self): + """ + Build and return a new class with a `__slots__` attribute. + """ + base_names = self._base_names + cd = { + k: v + for k, v in iteritems(self._cls_dict) + if k not in tuple(self._attr_names) + ("__dict__", "__weakref__") + } + + # If our class doesn't have its own implementation of __setattr__ + # (either from the user or by us), check the bases, if one of them has + # an attrs-made __setattr__, that needs to be reset. We don't walk the + # MRO because we only care about our immediate base classes. + # XXX: This can be confused by subclassing a slotted attrs class with + # XXX: a non-attrs class and subclass the resulting class with an attrs + # XXX: class. See `test_slotted_confused` for details. For now that's + # XXX: OK with us. + if not self._has_own_setattr: + cd["__attrs_own_setattr__"] = False + + if not self._has_custom_setattr: + for base_cls in self._cls.__bases__: + if base_cls.__dict__.get("__attrs_own_setattr__", False): + cd["__setattr__"] = object.__setattr__ + break + + # Traverse the MRO to check for an existing __weakref__. + weakref_inherited = False + for base_cls in self._cls.__mro__[1:-1]: + if base_cls.__dict__.get("__weakref__", None) is not None: + weakref_inherited = True + break + + names = self._attr_names + if ( + self._weakref_slot + and "__weakref__" not in getattr(self._cls, "__slots__", ()) + and "__weakref__" not in names + and not weakref_inherited + ): + names += ("__weakref__",) + + # We only add the names of attributes that aren't inherited. + # Setting __slots__ to inherited attributes wastes memory. + slot_names = [name for name in names if name not in base_names] + if self._cache_hash: + slot_names.append(_hash_cache_field) + cd["__slots__"] = tuple(slot_names) + + qualname = getattr(self._cls, "__qualname__", None) + if qualname is not None: + cd["__qualname__"] = qualname + + # Create new class based on old class and our methods. + cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) + + # The following is a fix for + # https://github.com/python-attrs/attrs/issues/102. On Python 3, + # if a method mentions `__class__` or uses the no-arg super(), the + # compiler will bake a reference to the class in the method itself + # as `method.__closure__`. Since we replace the class with a + # clone, we rewrite these references so it keeps working. + for item in cls.__dict__.values(): + if isinstance(item, (classmethod, staticmethod)): + # Class- and staticmethods hide their functions inside. + # These might need to be rewritten as well. + closure_cells = getattr(item.__func__, "__closure__", None) + else: + closure_cells = getattr(item, "__closure__", None) + + if not closure_cells: # Catch None or the empty list. + continue + for cell in closure_cells: + try: + match = cell.cell_contents is self._cls + except ValueError: # ValueError: Cell is empty + pass + else: + if match: + set_closure_cell(cell, cls) + + return cls + + def add_repr(self, ns): + self._cls_dict["__repr__"] = self._add_method_dunders( + _make_repr(self._attrs, ns=ns) + ) + return self + + def add_str(self): + repr = self._cls_dict.get("__repr__") + if repr is None: + raise ValueError( + "__str__ can only be generated if a __repr__ exists." + ) + + def __str__(self): + return self.__repr__() + + self._cls_dict["__str__"] = self._add_method_dunders(__str__) + return self + + def _make_getstate_setstate(self): + """ + Create custom __setstate__ and __getstate__ methods. + """ + # __weakref__ is not writable. + state_attr_names = tuple( + an for an in self._attr_names if an != "__weakref__" + ) + + def slots_getstate(self): + """ + Automatically created by attrs. + """ + return tuple(getattr(self, name) for name in state_attr_names) + + hash_caching_enabled = self._cache_hash + + def slots_setstate(self, state): + """ + Automatically created by attrs. + """ + __bound_setattr = _obj_setattr.__get__(self, Attribute) + for name, value in zip(state_attr_names, state): + __bound_setattr(name, value) + + # The hash code cache is not included when the object is + # serialized, but it still needs to be initialized to None to + # indicate that the first call to __hash__ should be a cache + # miss. + if hash_caching_enabled: + __bound_setattr(_hash_cache_field, None) + + return slots_getstate, slots_setstate + + def make_unhashable(self): + self._cls_dict["__hash__"] = None + return self + + def add_hash(self): + self._cls_dict["__hash__"] = self._add_method_dunders( + _make_hash( + self._cls, + self._attrs, + frozen=self._frozen, + cache_hash=self._cache_hash, + ) + ) + + return self + + def add_init(self): + self._cls_dict["__init__"] = self._add_method_dunders( + _make_init( + self._cls, + self._attrs, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr is not None + and self._on_setattr is not setters.NO_OP, + ) + ) + + return self + + def add_eq(self): + cd = self._cls_dict + + cd["__eq__"] = self._add_method_dunders( + _make_eq(self._cls, self._attrs) + ) + cd["__ne__"] = self._add_method_dunders(_make_ne()) + + return self + + def add_order(self): + cd = self._cls_dict + + cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = ( + self._add_method_dunders(meth) + for meth in _make_order(self._cls, self._attrs) + ) + + return self + + def add_setattr(self): + if self._frozen: + return self + + sa_attrs = {} + for a in self._attrs: + on_setattr = a.on_setattr or self._on_setattr + if on_setattr and on_setattr is not setters.NO_OP: + sa_attrs[a.name] = a, on_setattr + + if not sa_attrs: + return self + + if self._has_custom_setattr: + # We need to write a __setattr__ but there already is one! + raise ValueError( + "Can't combine custom __setattr__ with on_setattr hooks." + ) + + # docstring comes from _add_method_dunders + def __setattr__(self, name, val): + try: + a, hook = sa_attrs[name] + except KeyError: + nval = val + else: + nval = hook(self, a, val) + + _obj_setattr(self, name, nval) + + self._cls_dict["__attrs_own_setattr__"] = True + self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__) + self._has_own_setattr = True + + return self + + def _add_method_dunders(self, method): + """ + Add __module__ and __qualname__ to a *method* if possible. + """ + try: + method.__module__ = self._cls.__module__ + except AttributeError: + pass + + try: + method.__qualname__ = ".".join( + (self._cls.__qualname__, method.__name__) + ) + except AttributeError: + pass + + try: + method.__doc__ = "Method generated by attrs for class %s." % ( + self._cls.__qualname__, + ) + except AttributeError: + pass + + return method + + +_CMP_DEPRECATION = ( + "The usage of `cmp` is deprecated and will be removed on or after " + "2021-06-01. Please use `eq` and `order` instead." +) + + +def _determine_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + raise ValueError("Don't mix `cmp` with `eq' and `order`.") + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + warnings.warn(_CMP_DEPRECATION, DeprecationWarning, stacklevel=3) + + return cmp, cmp + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq = default_eq + + if order is None: + order = eq + + if eq is False and order is True: + raise ValueError("`order` can only be True if `eq` is True too.") + + return eq, order + + +def _determine_whether_to_implement( + cls, flag, auto_detect, dunders, default=True +): + """ + Check whether we should implement a set of methods for *cls*. + + *flag* is the argument passed into @attr.s like 'init', *auto_detect* the + same as passed into @attr.s and *dunders* is a tuple of attribute names + whose presence signal that the user has implemented it themselves. + + Return *default* if no reason for either for or against is found. + + auto_detect must be False on Python 2. + """ + if flag is True or flag is False: + return flag + + if flag is None and auto_detect is False: + return default + + # Logically, flag is None and auto_detect is True here. + for dunder in dunders: + if _has_own_attribute(cls, dunder): + return False + + return default + + +def attrs( + maybe_cls=None, + these=None, + repr_ns=None, + repr=None, + cmp=None, + hash=None, + init=None, + slots=False, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=False, + kw_only=False, + cache_hash=False, + auto_exc=False, + eq=None, + order=None, + auto_detect=False, + collect_by_mro=False, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, +): + r""" + A class decorator that adds `dunder + `_\ -methods according to the + specified attributes using `attr.ib` or the *these* argument. + + :param these: A dictionary of name to `attr.ib` mappings. This is + useful to avoid the definition of your attributes within the class body + because you can't (e.g. if you want to add ``__repr__`` methods to + Django models) or don't want to. + + If *these* is not ``None``, ``attrs`` will *not* search the class body + for attributes and will *not* remove any attributes from it. + + If *these* is an ordered dict (`dict` on Python 3.6+, + `collections.OrderedDict` otherwise), the order is deduced from + the order of the attributes inside *these*. Otherwise the order + of the definition of the attributes is used. + + :type these: `dict` of `str` to `attr.ib` + + :param str repr_ns: When using nested classes, there's no way in Python 2 + to automatically detect that. Therefore it's possible to set the + namespace explicitly for a more meaningful ``repr`` output. + :param bool auto_detect: Instead of setting the *init*, *repr*, *eq*, + *order*, and *hash* arguments explicitly, assume they are set to + ``True`` **unless any** of the involved methods for one of the + arguments is implemented in the *current* class (i.e. it is *not* + inherited from some base class). + + So for example by implementing ``__eq__`` on a class yourself, + ``attrs`` will deduce ``eq=False`` and won't create *neither* + ``__eq__`` *nor* ``__ne__`` (but Python classes come with a sensible + ``__ne__`` by default, so it *should* be enough to only implement + ``__eq__`` in most cases). + + .. warning:: + + If you prevent ``attrs`` from creating the ordering methods for you + (``order=False``, e.g. by implementing ``__le__``), it becomes + *your* responsibility to make sure its ordering is sound. The best + way is to use the `functools.total_ordering` decorator. + + + Passing ``True`` or ``False`` to *init*, *repr*, *eq*, *order*, + *cmp*, or *hash* overrides whatever *auto_detect* would determine. + + *auto_detect* requires Python 3. Setting it ``True`` on Python 2 raises + a `PythonTooOldError`. + + :param bool repr: Create a ``__repr__`` method with a human readable + representation of ``attrs`` attributes.. + :param bool str: Create a ``__str__`` method that is identical to + ``__repr__``. This is usually not necessary except for + `Exception`\ s. + :param Optional[bool] eq: If ``True`` or ``None`` (default), add ``__eq__`` + and ``__ne__`` methods that check two instances for equality. + + They compare the instances as if they were tuples of their ``attrs`` + attributes if and only if the types of both classes are *identical*! + :param Optional[bool] order: If ``True``, add ``__lt__``, ``__le__``, + ``__gt__``, and ``__ge__`` methods that behave like *eq* above and + allow instances to be ordered. If ``None`` (default) mirror value of + *eq*. + :param Optional[bool] cmp: Setting to ``True`` is equivalent to setting + ``eq=True, order=True``. Deprecated in favor of *eq* and *order*, has + precedence over them for backward-compatibility though. Must not be + mixed with *eq* or *order*. + :param Optional[bool] hash: If ``None`` (default), the ``__hash__`` method + is generated according how *eq* and *frozen* are set. + + 1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you. + 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set to + None, marking it unhashable (which it is). + 3. If *eq* is False, ``__hash__`` will be left untouched meaning the + ``__hash__`` method of the base class will be used (if base class is + ``object``, this means it will fall back to id-based hashing.). + + Although not recommended, you can decide for yourself and force + ``attrs`` to create one (e.g. if the class is immutable even though you + didn't freeze it programmatically) by passing ``True`` or not. Both of + these cases are rather special and should be used carefully. + + See our documentation on `hashing`, Python's documentation on + `object.__hash__`, and the `GitHub issue that led to the default \ + behavior `_ for more + details. + :param bool init: Create a ``__init__`` method that initializes the + ``attrs`` attributes. Leading underscores are stripped for the + argument name. If a ``__attrs_post_init__`` method exists on the + class, it will be called after the class is fully initialized. + :param bool slots: Create a `slotted class ` that's more + memory-efficient. Slotted classes are generally superior to the default + dict classes, but have some gotchas you should know about, so we + encourage you to read the `glossary entry `. + :param bool frozen: Make instances immutable after initialization. If + someone attempts to modify a frozen instance, + `attr.exceptions.FrozenInstanceError` is raised. + + .. note:: + + 1. This is achieved by installing a custom ``__setattr__`` method + on your class, so you can't implement your own. + + 2. True immutability is impossible in Python. + + 3. This *does* have a minor a runtime performance `impact + ` when initializing new instances. In other words: + ``__init__`` is slightly slower with ``frozen=True``. + + 4. If a class is frozen, you cannot modify ``self`` in + ``__attrs_post_init__`` or a self-written ``__init__``. You can + circumvent that limitation by using + ``object.__setattr__(self, "attribute_name", value)``. + + 5. Subclasses of a frozen class are frozen too. + + :param bool weakref_slot: Make instances weak-referenceable. This has no + effect unless ``slots`` is also enabled. + :param bool auto_attribs: If ``True``, collect `PEP 526`_-annotated + attributes (Python 3.6 and later only) from the class body. + + In this case, you **must** annotate every field. If ``attrs`` + encounters a field that is set to an `attr.ib` but lacks a type + annotation, an `attr.exceptions.UnannotatedAttributeError` is + raised. Use ``field_name: typing.Any = attr.ib(...)`` if you don't + want to set a type. + + If you assign a value to those attributes (e.g. ``x: int = 42``), that + value becomes the default value like if it were passed using + ``attr.ib(default=42)``. Passing an instance of `Factory` also + works as expected. + + Attributes annotated as `typing.ClassVar`, and attributes that are + neither annotated nor set to an `attr.ib` are **ignored**. + + .. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/ + :param bool kw_only: Make all attributes keyword-only (Python 3+) + in the generated ``__init__`` (if ``init`` is ``False``, this + parameter is ignored). + :param bool cache_hash: Ensure that the object's hash code is computed + only once and stored on the object. If this is set to ``True``, + hashing must be either explicitly or implicitly enabled for this + class. If the hash code is cached, avoid any reassignments of + fields involved in hash code computation or mutations of the objects + those fields point to after object creation. If such changes occur, + the behavior of the object's hash code is undefined. + :param bool auto_exc: If the class subclasses `BaseException` + (which implicitly includes any subclass of any exception), the + following happens to behave like a well-behaved Python exceptions + class: + + - the values for *eq*, *order*, and *hash* are ignored and the + instances compare and hash by the instance's ids (N.B. ``attrs`` will + *not* remove existing implementations of ``__hash__`` or the equality + methods. It just won't add own ones.), + - all attributes that are either passed into ``__init__`` or have a + default value are additionally available as a tuple in the ``args`` + attribute, + - the value of *str* is ignored leaving ``__str__`` to base classes. + :param bool collect_by_mro: Setting this to `True` fixes the way ``attrs`` + collects attributes from base classes. The default behavior is + incorrect in certain cases of multiple inheritance. It should be on by + default but is kept off for backward-compatability. + + See issue `#428 `_ for + more details. + + :param Optional[bool] getstate_setstate: + .. note:: + This is usually only interesting for slotted classes and you should + probably just set *auto_detect* to `True`. + + If `True`, ``__getstate__`` and + ``__setstate__`` are generated and attached to the class. This is + necessary for slotted classes to be pickleable. If left `None`, it's + `True` by default for slotted classes and ``False`` for dict classes. + + If *auto_detect* is `True`, and *getstate_setstate* is left `None`, + and **either** ``__getstate__`` or ``__setstate__`` is detected directly + on the class (i.e. not inherited), it is set to `False` (this is usually + what you want). + + :param on_setattr: A callable that is run whenever the user attempts to set + an attribute (either by assignment like ``i.x = 42`` or by using + `setattr` like ``setattr(i, "x", 42)``). It receives the same arguments + as validators: the instance, the attribute that is being modified, and + the new value. + + If no exception is raised, the attribute is set to the return value of + the callable. + + If a list of callables is passed, they're automatically wrapped in an + `attr.setters.pipe`. + + :param Optional[callable] field_transformer: + A function that is called with the original class object and all + fields right before ``attrs`` finalizes the class. You can use + this, e.g., to automatically add converters or validators to + fields based on their types. See `transform-fields` for more details. + + .. versionadded:: 16.0.0 *slots* + .. versionadded:: 16.1.0 *frozen* + .. versionadded:: 16.3.0 *str* + .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``. + .. versionchanged:: 17.1.0 + *hash* supports ``None`` as value which is also the default now. + .. versionadded:: 17.3.0 *auto_attribs* + .. versionchanged:: 18.1.0 + If *these* is passed, no attributes are deleted from the class body. + .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained. + .. versionadded:: 18.2.0 *weakref_slot* + .. deprecated:: 18.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a + `DeprecationWarning` if the classes compared are subclasses of + each other. ``__eq`` and ``__ne__`` never tried to compared subclasses + to each other. + .. versionchanged:: 19.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider + subclasses comparable anymore. + .. versionadded:: 18.2.0 *kw_only* + .. versionadded:: 18.2.0 *cache_hash* + .. versionadded:: 19.1.0 *auto_exc* + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *auto_detect* + .. versionadded:: 20.1.0 *collect_by_mro* + .. versionadded:: 20.1.0 *getstate_setstate* + .. versionadded:: 20.1.0 *on_setattr* + .. versionadded:: 20.3.0 *field_transformer* + """ + if auto_detect and PY2: + raise PythonTooOldError( + "auto_detect only works on Python 3 and later." + ) + + eq_, order_ = _determine_eq_order(cmp, eq, order, None) + hash_ = hash # work around the lack of nonlocal + + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + def wrap(cls): + + if getattr(cls, "__class__", None) is None: + raise TypeError("attrs only works with new-style classes.") + + is_frozen = frozen or _has_frozen_base_class(cls) + is_exc = auto_exc is True and issubclass(cls, BaseException) + has_own_setattr = auto_detect and _has_own_attribute( + cls, "__setattr__" + ) + + if has_own_setattr and is_frozen: + raise ValueError("Can't freeze a class with a custom __setattr__.") + + builder = _ClassBuilder( + cls, + these, + slots, + is_frozen, + weakref_slot, + _determine_whether_to_implement( + cls, + getstate_setstate, + auto_detect, + ("__getstate__", "__setstate__"), + default=slots, + ), + auto_attribs, + kw_only, + cache_hash, + is_exc, + collect_by_mro, + on_setattr, + has_own_setattr, + field_transformer, + ) + if _determine_whether_to_implement( + cls, repr, auto_detect, ("__repr__",) + ): + builder.add_repr(repr_ns) + if str is True: + builder.add_str() + + eq = _determine_whether_to_implement( + cls, eq_, auto_detect, ("__eq__", "__ne__") + ) + if not is_exc and eq is True: + builder.add_eq() + if not is_exc and _determine_whether_to_implement( + cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__") + ): + builder.add_order() + + builder.add_setattr() + + if ( + hash_ is None + and auto_detect is True + and _has_own_attribute(cls, "__hash__") + ): + hash = False + else: + hash = hash_ + if hash is not True and hash is not False and hash is not None: + # Can't use `hash in` because 1 == True for example. + raise TypeError( + "Invalid value for hash. Must be True, False, or None." + ) + elif hash is False or (hash is None and eq is False) or is_exc: + # Don't do anything. Should fall back to __object__'s __hash__ + # which is by id. + if cache_hash: + raise TypeError( + "Invalid value for cache_hash. To use hash caching," + " hashing must be either explicitly or implicitly " + "enabled." + ) + elif hash is True or ( + hash is None and eq is True and is_frozen is True + ): + # Build a __hash__ if told so, or if it's safe. + builder.add_hash() + else: + # Raise TypeError on attempts to hash. + if cache_hash: + raise TypeError( + "Invalid value for cache_hash. To use hash caching," + " hashing must be either explicitly or implicitly " + "enabled." + ) + builder.make_unhashable() + + if _determine_whether_to_implement( + cls, init, auto_detect, ("__init__",) + ): + builder.add_init() + else: + if cache_hash: + raise TypeError( + "Invalid value for cache_hash. To use hash caching," + " init must be True." + ) + + return builder.build_class() + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but ``None`` if used as `@attrs()`. + if maybe_cls is None: + return wrap + else: + return wrap(maybe_cls) + + +_attrs = attrs +""" +Internal alias so we can use it in functions that take an argument called +*attrs*. +""" + + +if PY2: + + def _has_frozen_base_class(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return ( + getattr(cls.__setattr__, "__module__", None) + == _frozen_setattrs.__module__ + and cls.__setattr__.__name__ == _frozen_setattrs.__name__ + ) + + +else: + + def _has_frozen_base_class(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return cls.__setattr__ == _frozen_setattrs + + +def _attrs_to_tuple(obj, attrs): + """ + Create a tuple of all values of *obj*'s *attrs*. + """ + return tuple(getattr(obj, a.name) for a in attrs) + + +def _generate_unique_filename(cls, func_name): + """ + Create a "filename" suitable for a function being generated. + """ + unique_id = uuid.uuid4() + extra = "" + count = 1 + + while True: + unique_filename = "".format( + func_name, + cls.__module__, + getattr(cls, "__qualname__", cls.__name__), + extra, + ) + # To handle concurrency we essentially "reserve" our spot in + # the linecache with a dummy line. The caller can then + # set this value correctly. + cache_line = (1, None, (str(unique_id),), unique_filename) + if ( + linecache.cache.setdefault(unique_filename, cache_line) + == cache_line + ): + return unique_filename + + # Looks like this spot is taken. Try again. + count += 1 + extra = "-{0}".format(count) + + +def _make_hash(cls, attrs, frozen, cache_hash): + attrs = tuple( + a for a in attrs if a.hash is True or (a.hash is None and a.eq is True) + ) + + tab = " " + + unique_filename = _generate_unique_filename(cls, "hash") + type_hash = hash(unique_filename) + + hash_def = "def __hash__(self" + hash_func = "hash((" + closing_braces = "))" + if not cache_hash: + hash_def += "):" + else: + if not PY2: + hash_def += ", *" + + hash_def += ( + ", _cache_wrapper=" + + "__import__('attr._make')._make._CacheHashWrapper):" + ) + hash_func = "_cache_wrapper(" + hash_func + closing_braces += ")" + + method_lines = [hash_def] + + def append_hash_computation_lines(prefix, indent): + """ + Generate the code for actually computing the hash code. + Below this will either be returned directly or used to compute + a value which is then cached, depending on the value of cache_hash + """ + + method_lines.extend( + [ + indent + prefix + hash_func, + indent + " %d," % (type_hash,), + ] + ) + + for a in attrs: + method_lines.append(indent + " self.%s," % a.name) + + method_lines.append(indent + " " + closing_braces) + + if cache_hash: + method_lines.append(tab + "if self.%s is None:" % _hash_cache_field) + if frozen: + append_hash_computation_lines( + "object.__setattr__(self, '%s', " % _hash_cache_field, tab * 2 + ) + method_lines.append(tab * 2 + ")") # close __setattr__ + else: + append_hash_computation_lines( + "self.%s = " % _hash_cache_field, tab * 2 + ) + method_lines.append(tab + "return self.%s" % _hash_cache_field) + else: + append_hash_computation_lines("return ", tab) + + script = "\n".join(method_lines) + globs = {} + locs = {} + bytecode = compile(script, unique_filename, "exec") + eval(bytecode, globs, locs) + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + linecache.cache[unique_filename] = ( + len(script), + None, + script.splitlines(True), + unique_filename, + ) + + return locs["__hash__"] + + +def _add_hash(cls, attrs): + """ + Add a hash method to *cls*. + """ + cls.__hash__ = _make_hash(cls, attrs, frozen=False, cache_hash=False) + return cls + + +def _make_ne(): + """ + Create __ne__ method. + """ + + def __ne__(self, other): + """ + Check equality and either forward a NotImplemented or + return the result negated. + """ + result = self.__eq__(other) + if result is NotImplemented: + return NotImplemented + + return not result + + return __ne__ + + +def _make_eq(cls, attrs): + """ + Create __eq__ method for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.eq] + + unique_filename = _generate_unique_filename(cls, "eq") + lines = [ + "def __eq__(self, other):", + " if other.__class__ is not self.__class__:", + " return NotImplemented", + ] + # We can't just do a big self.x = other.x and... clause due to + # irregularities like nan == nan is false but (nan,) == (nan,) is true. + if attrs: + lines.append(" return (") + others = [" ) == ("] + for a in attrs: + lines.append(" self.%s," % (a.name,)) + others.append(" other.%s," % (a.name,)) + + lines += others + [" )"] + else: + lines.append(" return True") + + script = "\n".join(lines) + globs = {} + locs = {} + bytecode = compile(script, unique_filename, "exec") + eval(bytecode, globs, locs) + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + linecache.cache[unique_filename] = ( + len(script), + None, + script.splitlines(True), + unique_filename, + ) + return locs["__eq__"] + + +def _make_order(cls, attrs): + """ + Create ordering methods for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.order] + + def attrs_to_tuple(obj): + """ + Save us some typing. + """ + return _attrs_to_tuple(obj, attrs) + + def __lt__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) < attrs_to_tuple(other) + + return NotImplemented + + def __le__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) <= attrs_to_tuple(other) + + return NotImplemented + + def __gt__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) > attrs_to_tuple(other) + + return NotImplemented + + def __ge__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) >= attrs_to_tuple(other) + + return NotImplemented + + return __lt__, __le__, __gt__, __ge__ + + +def _add_eq(cls, attrs=None): + """ + Add equality methods to *cls* with *attrs*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + cls.__eq__ = _make_eq(cls, attrs) + cls.__ne__ = _make_ne() + + return cls + + +_already_repring = threading.local() + + +def _make_repr(attrs, ns): + """ + Make a repr method that includes relevant *attrs*, adding *ns* to the full + name. + """ + + # Figure out which attributes to include, and which function to use to + # format them. The a.repr value can be either bool or a custom callable. + attr_names_with_reprs = tuple( + (a.name, repr if a.repr is True else a.repr) + for a in attrs + if a.repr is not False + ) + + def __repr__(self): + """ + Automatically created by attrs. + """ + try: + working_set = _already_repring.working_set + except AttributeError: + working_set = set() + _already_repring.working_set = working_set + + if id(self) in working_set: + return "..." + real_cls = self.__class__ + if ns is None: + qualname = getattr(real_cls, "__qualname__", None) + if qualname is not None: + class_name = qualname.rsplit(">.", 1)[-1] + else: + class_name = real_cls.__name__ + else: + class_name = ns + "." + real_cls.__name__ + + # Since 'self' remains on the stack (i.e.: strongly referenced) for the + # duration of this call, it's safe to depend on id(...) stability, and + # not need to track the instance and therefore worry about properties + # like weakref- or hash-ability. + working_set.add(id(self)) + try: + result = [class_name, "("] + first = True + for name, attr_repr in attr_names_with_reprs: + if first: + first = False + else: + result.append(", ") + result.extend( + (name, "=", attr_repr(getattr(self, name, NOTHING))) + ) + return "".join(result) + ")" + finally: + working_set.remove(id(self)) + + return __repr__ + + +def _add_repr(cls, ns=None, attrs=None): + """ + Add a repr method to *cls*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + cls.__repr__ = _make_repr(attrs, ns) + return cls + + +def fields(cls): + """ + Return the tuple of ``attrs`` attributes for a class. + + The tuple also allows accessing the fields by their names (see below for + examples). + + :param type cls: Class to introspect. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + :rtype: tuple (with name accessors) of `attr.Attribute` + + .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields + by name. + """ + if not isclass(cls): + raise TypeError("Passed object must be a class.") + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: + raise NotAnAttrsClassError( + "{cls!r} is not an attrs-decorated class.".format(cls=cls) + ) + return attrs + + +def fields_dict(cls): + """ + Return an ordered dictionary of ``attrs`` attributes for a class, whose + keys are the attribute names. + + :param type cls: Class to introspect. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + :rtype: an ordered dict where keys are attribute names and values are + `attr.Attribute`\\ s. This will be a `dict` if it's + naturally ordered like on Python 3.6+ or an + :class:`~collections.OrderedDict` otherwise. + + .. versionadded:: 18.1.0 + """ + if not isclass(cls): + raise TypeError("Passed object must be a class.") + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: + raise NotAnAttrsClassError( + "{cls!r} is not an attrs-decorated class.".format(cls=cls) + ) + return ordered_dict(((a.name, a) for a in attrs)) + + +def validate(inst): + """ + Validate all attributes on *inst* that have a validator. + + Leaves all exceptions through. + + :param inst: Instance of a class with ``attrs`` attributes. + """ + if _config._run_validators is False: + return + + for a in fields(inst.__class__): + v = a.validator + if v is not None: + v(inst, a, getattr(inst, a.name)) + + +def _is_slot_cls(cls): + return "__slots__" in cls.__dict__ + + +def _is_slot_attr(a_name, base_attr_map): + """ + Check if the attribute name comes from a slot class. + """ + return a_name in base_attr_map and _is_slot_cls(base_attr_map[a_name]) + + +def _make_init( + cls, + attrs, + post_init, + frozen, + slots, + cache_hash, + base_attr_map, + is_exc, + has_global_on_setattr, +): + if frozen and has_global_on_setattr: + raise ValueError("Frozen classes can't use on_setattr.") + + needs_cached_setattr = cache_hash or frozen + filtered_attrs = [] + attr_dict = {} + for a in attrs: + if not a.init and a.default is NOTHING: + continue + + filtered_attrs.append(a) + attr_dict[a.name] = a + + if a.on_setattr is not None: + if frozen is True: + raise ValueError("Frozen classes can't use on_setattr.") + + needs_cached_setattr = True + elif ( + has_global_on_setattr and a.on_setattr is not setters.NO_OP + ) or _is_slot_attr(a.name, base_attr_map): + needs_cached_setattr = True + + unique_filename = _generate_unique_filename(cls, "init") + + script, globs, annotations = _attrs_to_init_script( + filtered_attrs, + frozen, + slots, + post_init, + cache_hash, + base_attr_map, + is_exc, + needs_cached_setattr, + has_global_on_setattr, + ) + locs = {} + bytecode = compile(script, unique_filename, "exec") + globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict}) + + if needs_cached_setattr: + # Save the lookup overhead in __init__ if we need to circumvent + # setattr hooks. + globs["_cached_setattr"] = _obj_setattr + + eval(bytecode, globs, locs) + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + linecache.cache[unique_filename] = ( + len(script), + None, + script.splitlines(True), + unique_filename, + ) + + __init__ = locs["__init__"] + __init__.__annotations__ = annotations + + return __init__ + + +def _setattr(attr_name, value_var, has_on_setattr): + """ + Use the cached object.setattr to set *attr_name* to *value_var*. + """ + return "_setattr('%s', %s)" % (attr_name, value_var) + + +def _setattr_with_converter(attr_name, value_var, has_on_setattr): + """ + Use the cached object.setattr to set *attr_name* to *value_var*, but run + its converter first. + """ + return "_setattr('%s', %s(%s))" % ( + attr_name, + _init_converter_pat % (attr_name,), + value_var, + ) + + +def _assign(attr_name, value, has_on_setattr): + """ + Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise + relegate to _setattr. + """ + if has_on_setattr: + return _setattr(attr_name, value, True) + + return "self.%s = %s" % (attr_name, value) + + +def _assign_with_converter(attr_name, value_var, has_on_setattr): + """ + Unless *attr_name* has an on_setattr hook, use normal assignment after + conversion. Otherwise relegate to _setattr_with_converter. + """ + if has_on_setattr: + return _setattr_with_converter(attr_name, value_var, True) + + return "self.%s = %s(%s)" % ( + attr_name, + _init_converter_pat % (attr_name,), + value_var, + ) + + +if PY2: + + def _unpack_kw_only_py2(attr_name, default=None): + """ + Unpack *attr_name* from _kw_only dict. + """ + if default is not None: + arg_default = ", %s" % default + else: + arg_default = "" + return "%s = _kw_only.pop('%s'%s)" % ( + attr_name, + attr_name, + arg_default, + ) + + def _unpack_kw_only_lines_py2(kw_only_args): + """ + Unpack all *kw_only_args* from _kw_only dict and handle errors. + + Given a list of strings "{attr_name}" and "{attr_name}={default}" + generates list of lines of code that pop attrs from _kw_only dict and + raise TypeError similar to builtin if required attr is missing or + extra key is passed. + + >>> print("\n".join(_unpack_kw_only_lines_py2(["a", "b=42"]))) + try: + a = _kw_only.pop('a') + b = _kw_only.pop('b', 42) + except KeyError as _key_error: + raise TypeError( + ... + if _kw_only: + raise TypeError( + ... + """ + lines = ["try:"] + lines.extend( + " " + _unpack_kw_only_py2(*arg.split("=")) + for arg in kw_only_args + ) + lines += """\ +except KeyError as _key_error: + raise TypeError( + '__init__() missing required keyword-only argument: %s' % _key_error + ) +if _kw_only: + raise TypeError( + '__init__() got an unexpected keyword argument %r' + % next(iter(_kw_only)) + ) +""".split( + "\n" + ) + return lines + + +def _attrs_to_init_script( + attrs, + frozen, + slots, + post_init, + cache_hash, + base_attr_map, + is_exc, + needs_cached_setattr, + has_global_on_setattr, +): + """ + Return a script of an initializer for *attrs* and a dict of globals. + + The globals are expected by the generated script. + + If *frozen* is True, we cannot set the attributes directly so we use + a cached ``object.__setattr__``. + """ + lines = [] + if needs_cached_setattr: + lines.append( + # Circumvent the __setattr__ descriptor to save one lookup per + # assignment. + # Note _setattr will be used again below if cache_hash is True + "_setattr = _cached_setattr.__get__(self, self.__class__)" + ) + + if frozen is True: + if slots is True: + fmt_setter = _setattr + fmt_setter_with_converter = _setattr_with_converter + else: + # Dict frozen classes assign directly to __dict__. + # But only if the attribute doesn't come from an ancestor slot + # class. + # Note _inst_dict will be used again below if cache_hash is True + lines.append("_inst_dict = self.__dict__") + + def fmt_setter(attr_name, value_var, has_on_setattr): + if _is_slot_attr(attr_name, base_attr_map): + return _setattr(attr_name, value_var, has_on_setattr) + + return "_inst_dict['%s'] = %s" % (attr_name, value_var) + + def fmt_setter_with_converter( + attr_name, value_var, has_on_setattr + ): + if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): + return _setattr_with_converter( + attr_name, value_var, has_on_setattr + ) + + return "_inst_dict['%s'] = %s(%s)" % ( + attr_name, + _init_converter_pat % (attr_name,), + value_var, + ) + + else: + # Not frozen. + fmt_setter = _assign + fmt_setter_with_converter = _assign_with_converter + + args = [] + kw_only_args = [] + attrs_to_validate = [] + + # This is a dictionary of names to validator and converter callables. + # Injecting this into __init__ globals lets us avoid lookups. + names_for_globals = {} + annotations = {"return": None} + + for a in attrs: + if a.validator: + attrs_to_validate.append(a) + + attr_name = a.name + has_on_setattr = a.on_setattr is not None or ( + a.on_setattr is not setters.NO_OP and has_global_on_setattr + ) + arg_name = a.name.lstrip("_") + + has_factory = isinstance(a.default, Factory) + if has_factory and a.default.takes_self: + maybe_self = "self" + else: + maybe_self = "" + + if a.init is False: + if has_factory: + init_factory_name = _init_factory_pat.format(a.name) + if a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + init_factory_name + "(%s)" % (maybe_self,), + has_on_setattr, + ) + ) + conv_name = _init_converter_pat % (a.name,) + names_for_globals[conv_name] = a.converter + else: + lines.append( + fmt_setter( + attr_name, + init_factory_name + "(%s)" % (maybe_self,), + has_on_setattr, + ) + ) + names_for_globals[init_factory_name] = a.default.factory + else: + if a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + "attr_dict['%s'].default" % (attr_name,), + has_on_setattr, + ) + ) + conv_name = _init_converter_pat % (a.name,) + names_for_globals[conv_name] = a.converter + else: + lines.append( + fmt_setter( + attr_name, + "attr_dict['%s'].default" % (attr_name,), + has_on_setattr, + ) + ) + elif a.default is not NOTHING and not has_factory: + arg = "%s=attr_dict['%s'].default" % (arg_name, attr_name) + if a.kw_only: + kw_only_args.append(arg) + else: + args.append(arg) + + if a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr + ) + ) + names_for_globals[ + _init_converter_pat % (a.name,) + ] = a.converter + else: + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + + elif has_factory: + arg = "%s=NOTHING" % (arg_name,) + if a.kw_only: + kw_only_args.append(arg) + else: + args.append(arg) + lines.append("if %s is not NOTHING:" % (arg_name,)) + + init_factory_name = _init_factory_pat.format(a.name) + if a.converter is not None: + lines.append( + " " + + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr + ) + ) + lines.append("else:") + lines.append( + " " + + fmt_setter_with_converter( + attr_name, + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, + ) + ) + names_for_globals[ + _init_converter_pat % (a.name,) + ] = a.converter + else: + lines.append( + " " + fmt_setter(attr_name, arg_name, has_on_setattr) + ) + lines.append("else:") + lines.append( + " " + + fmt_setter( + attr_name, + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, + ) + ) + names_for_globals[init_factory_name] = a.default.factory + else: + if a.kw_only: + kw_only_args.append(arg_name) + else: + args.append(arg_name) + + if a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr + ) + ) + names_for_globals[ + _init_converter_pat % (a.name,) + ] = a.converter + else: + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + + if a.init is True and a.converter is None and a.type is not None: + annotations[arg_name] = a.type + + if attrs_to_validate: # we can skip this if there are no validators. + names_for_globals["_config"] = _config + lines.append("if _config._run_validators is True:") + for a in attrs_to_validate: + val_name = "__attr_validator_" + a.name + attr_name = "__attr_" + a.name + lines.append( + " %s(self, %s, self.%s)" % (val_name, attr_name, a.name) + ) + names_for_globals[val_name] = a.validator + names_for_globals[attr_name] = a + + if post_init: + lines.append("self.__attrs_post_init__()") + + # because this is set only after __attrs_post_init is called, a crash + # will result if post-init tries to access the hash code. This seemed + # preferable to setting this beforehand, in which case alteration to + # field values during post-init combined with post-init accessing the + # hash code would result in silent bugs. + if cache_hash: + if frozen: + if slots: + # if frozen and slots, then _setattr defined above + init_hash_cache = "_setattr('%s', %s)" + else: + # if frozen and not slots, then _inst_dict defined above + init_hash_cache = "_inst_dict['%s'] = %s" + else: + init_hash_cache = "self.%s = %s" + lines.append(init_hash_cache % (_hash_cache_field, "None")) + + # For exceptions we rely on BaseException.__init__ for proper + # initialization. + if is_exc: + vals = ",".join("self." + a.name for a in attrs if a.init) + + lines.append("BaseException.__init__(self, %s)" % (vals,)) + + args = ", ".join(args) + if kw_only_args: + if PY2: + lines = _unpack_kw_only_lines_py2(kw_only_args) + lines + + args += "%s**_kw_only" % (", " if args else "",) # leading comma + else: + args += "%s*, %s" % ( + ", " if args else "", # leading comma + ", ".join(kw_only_args), # kw_only args + ) + return ( + """\ +def __init__(self, {args}): + {lines} +""".format( + args=args, lines="\n ".join(lines) if lines else "pass" + ), + names_for_globals, + annotations, + ) + + +class Attribute(object): + """ + *Read-only* representation of an attribute. + + Instances of this class are frequently used for introspection purposes + like: + + - `fields` returns a tuple of them. + - Validators get them passed as the first argument. + - The *field transformer* hook receives a list of them. + + :attribute name: The name of the attribute. + :attribute inherited: Whether or not that attribute has been inherited from + a base class. + + Plus *all* arguments of `attr.ib` (except for ``factory`` + which is only syntactic sugar for ``default=Factory(...)``. + + .. versionadded:: 20.1.0 *inherited* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.2.0 *inherited* is not taken into account for + equality checks and hashing anymore. + + For the full version history of the fields, see `attr.ib`. + """ + + __slots__ = ( + "name", + "default", + "validator", + "repr", + "eq", + "order", + "hash", + "init", + "metadata", + "type", + "converter", + "kw_only", + "inherited", + "on_setattr", + ) + + def __init__( + self, + name, + default, + validator, + repr, + cmp, # XXX: unused, remove along with other cmp code. + hash, + init, + inherited, + metadata=None, + type=None, + converter=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, + ): + eq, order = _determine_eq_order(cmp, eq, order, True) + + # Cache this descriptor here to speed things up later. + bound_setattr = _obj_setattr.__get__(self, Attribute) + + # Despite the big red warning, people *do* instantiate `Attribute` + # themselves. + bound_setattr("name", name) + bound_setattr("default", default) + bound_setattr("validator", validator) + bound_setattr("repr", repr) + bound_setattr("eq", eq) + bound_setattr("order", order) + bound_setattr("hash", hash) + bound_setattr("init", init) + bound_setattr("converter", converter) + bound_setattr( + "metadata", + ( + metadata_proxy(metadata) + if metadata + else _empty_metadata_singleton + ), + ) + bound_setattr("type", type) + bound_setattr("kw_only", kw_only) + bound_setattr("inherited", inherited) + bound_setattr("on_setattr", on_setattr) + + def __setattr__(self, name, value): + raise FrozenInstanceError() + + @classmethod + def from_counting_attr(cls, name, ca, type=None): + # type holds the annotated value. deal with conflicts: + if type is None: + type = ca.type + elif ca.type is not None: + raise ValueError( + "Type annotation and type argument cannot both be present" + ) + inst_dict = { + k: getattr(ca, k) + for k in Attribute.__slots__ + if k + not in ( + "name", + "validator", + "default", + "type", + "inherited", + ) # exclude methods and deprecated alias + } + return cls( + name=name, + validator=ca._validator, + default=ca._default, + type=type, + cmp=None, + inherited=False, + **inst_dict + ) + + @property + def cmp(self): + """ + Simulate the presence of a cmp attribute and warn. + """ + warnings.warn(_CMP_DEPRECATION, DeprecationWarning, stacklevel=2) + + return self.eq and self.order + + # Don't use attr.evolve since fields(Attribute) doesn't work + def evolve(self, **changes): + """ + Copy *self* and apply *changes*. + + This works similarly to `attr.evolve` but that function does not work + with ``Attribute``. + + It is mainly meant to be used for `transform-fields`. + + .. versionadded:: 20.3.0 + """ + new = copy.copy(self) + + new._setattrs(changes.items()) + + return new + + # Don't use _add_pickle since fields(Attribute) doesn't work + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple( + getattr(self, name) if name != "metadata" else dict(self.metadata) + for name in self.__slots__ + ) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + self._setattrs(zip(self.__slots__, state)) + + def _setattrs(self, name_values_pairs): + bound_setattr = _obj_setattr.__get__(self, Attribute) + for name, value in name_values_pairs: + if name != "metadata": + bound_setattr(name, value) + else: + bound_setattr( + name, + metadata_proxy(value) + if value + else _empty_metadata_singleton, + ) + + +_a = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=(name != "metadata"), + init=True, + inherited=False, + ) + for name in Attribute.__slots__ +] + +Attribute = _add_hash( + _add_eq( + _add_repr(Attribute, attrs=_a), + attrs=[a for a in _a if a.name != "inherited"], + ), + attrs=[a for a in _a if a.hash and a.name != "inherited"], +) + + +class _CountingAttr(object): + """ + Intermediate representation of attributes that uses a counter to preserve + the order in which the attributes have been defined. + + *Internal* data structure of the attrs library. Running into is most + likely the result of a bug like a forgotten `@attr.s` decorator. + """ + + __slots__ = ( + "counter", + "_default", + "repr", + "eq", + "order", + "hash", + "init", + "metadata", + "_validator", + "converter", + "type", + "kw_only", + "on_setattr", + ) + __attrs_attrs__ = tuple( + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=True, + init=True, + kw_only=False, + eq=True, + order=False, + inherited=False, + on_setattr=None, + ) + for name in ( + "counter", + "_default", + "repr", + "eq", + "order", + "hash", + "init", + "on_setattr", + ) + ) + ( + Attribute( + name="metadata", + default=None, + validator=None, + repr=True, + cmp=None, + hash=False, + init=True, + kw_only=False, + eq=True, + order=False, + inherited=False, + on_setattr=None, + ), + ) + cls_counter = 0 + + def __init__( + self, + default, + validator, + repr, + cmp, # XXX: unused, remove along with cmp + hash, + init, + converter, + metadata, + type, + kw_only, + eq, + order, + on_setattr, + ): + _CountingAttr.cls_counter += 1 + self.counter = _CountingAttr.cls_counter + self._default = default + self._validator = validator + self.converter = converter + self.repr = repr + self.eq = eq + self.order = order + self.hash = hash + self.init = init + self.metadata = metadata + self.type = type + self.kw_only = kw_only + self.on_setattr = on_setattr + + def validator(self, meth): + """ + Decorator that adds *meth* to the list of validators. + + Returns *meth* unchanged. + + .. versionadded:: 17.1.0 + """ + if self._validator is None: + self._validator = meth + else: + self._validator = and_(self._validator, meth) + return meth + + def default(self, meth): + """ + Decorator that allows to set the default for an attribute. + + Returns *meth* unchanged. + + :raises DefaultAlreadySetError: If default has been set before. + + .. versionadded:: 17.1.0 + """ + if self._default is not NOTHING: + raise DefaultAlreadySetError() + + self._default = Factory(meth, takes_self=True) + + return meth + + +_CountingAttr = _add_eq(_add_repr(_CountingAttr)) + + +@attrs(slots=True, init=False, hash=True) +class Factory(object): + """ + Stores a factory callable. + + If passed as the default value to `attr.ib`, the factory is used to + generate a new value. + + :param callable factory: A callable that takes either none or exactly one + mandatory positional argument depending on *takes_self*. + :param bool takes_self: Pass the partially initialized instance that is + being initialized as a positional argument. + + .. versionadded:: 17.1.0 *takes_self* + """ + + factory = attrib() + takes_self = attrib() + + def __init__(self, factory, takes_self=False): + """ + `Factory` is part of the default machinery so if we want a default + value here, we have to implement it ourselves. + """ + self.factory = factory + self.takes_self = takes_self + + +def make_class(name, attrs, bases=(object,), **attributes_arguments): + """ + A quick way to create a new class called *name* with *attrs*. + + :param str name: The name for the new class. + + :param attrs: A list of names or a dictionary of mappings of names to + attributes. + + If *attrs* is a list or an ordered dict (`dict` on Python 3.6+, + `collections.OrderedDict` otherwise), the order is deduced from + the order of the names or attributes inside *attrs*. Otherwise the + order of the definition of the attributes is used. + :type attrs: `list` or `dict` + + :param tuple bases: Classes that the new class will subclass. + + :param attributes_arguments: Passed unmodified to `attr.s`. + + :return: A new class with *attrs*. + :rtype: type + + .. versionadded:: 17.1.0 *bases* + .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. + """ + if isinstance(attrs, dict): + cls_dict = attrs + elif isinstance(attrs, (list, tuple)): + cls_dict = dict((a, attrib()) for a in attrs) + else: + raise TypeError("attrs argument must be a dict or a list.") + + post_init = cls_dict.pop("__attrs_post_init__", None) + type_ = type( + name, + bases, + {} if post_init is None else {"__attrs_post_init__": post_init}, + ) + # For pickling to work, the __module__ variable needs to be set to the + # frame where the class is created. Bypass this step in environments where + # sys._getframe is not defined (Jython for example) or sys._getframe is not + # defined for arguments greater than 0 (IronPython). + try: + type_.__module__ = sys._getframe(1).f_globals.get( + "__name__", "__main__" + ) + except (AttributeError, ValueError): + pass + + # We do it here for proper warnings with meaningful stacklevel. + cmp = attributes_arguments.pop("cmp", None) + ( + attributes_arguments["eq"], + attributes_arguments["order"], + ) = _determine_eq_order( + cmp, + attributes_arguments.get("eq"), + attributes_arguments.get("order"), + True, + ) + + return _attrs(these=cls_dict, **attributes_arguments)(type_) + + +# These are required by within this module so we define them here and merely +# import into .validators / .converters. + + +@attrs(slots=True, hash=True) +class _AndValidator(object): + """ + Compose many validators to a single one. + """ + + _validators = attrib() + + def __call__(self, inst, attr, value): + for v in self._validators: + v(inst, attr, value) + + +def and_(*validators): + """ + A validator that composes multiple validators into one. + + When called on a value, it runs all wrapped validators. + + :param callables validators: Arbitrary number of validators. + + .. versionadded:: 17.1.0 + """ + vals = [] + for validator in validators: + vals.extend( + validator._validators + if isinstance(validator, _AndValidator) + else [validator] + ) + + return _AndValidator(tuple(vals)) + + +def pipe(*converters): + """ + A converter that composes multiple converters into one. + + When called on a value, it runs all wrapped converters, returning the + *last* value. + + :param callables converters: Arbitrary number of converters. + + .. versionadded:: 20.1.0 + """ + + def pipe_converter(val): + for converter in converters: + val = converter(val) + + return val + + return pipe_converter diff --git a/robot/lib/python3.8/site-packages/attr/_next_gen.py b/robot/lib/python3.8/site-packages/attr/_next_gen.py new file mode 100644 index 0000000000000000000000000000000000000000..2b5565c569e5e1985187db8720d98c28bd3bfaa3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/_next_gen.py @@ -0,0 +1,160 @@ +""" +This is a Python 3.6 and later-only, keyword-only, and **provisional** API that +calls `attr.s` with different default values. + +Provisional APIs that shall become "import attrs" one glorious day. +""" + +from functools import partial + +from attr.exceptions import UnannotatedAttributeError + +from . import setters +from ._make import NOTHING, _frozen_setattrs, attrib, attrs + + +def define( + maybe_cls=None, + *, + these=None, + repr=None, + hash=None, + init=None, + slots=True, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=None, + kw_only=False, + cache_hash=False, + auto_exc=True, + eq=None, + order=False, + auto_detect=True, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, +): + r""" + The only behavioral differences are the handling of the *auto_attribs* + option: + + :param Optional[bool] auto_attribs: If set to `True` or `False`, it behaves + exactly like `attr.s`. If left `None`, `attr.s` will try to guess: + + 1. If all attributes are annotated and no `attr.ib` is found, it assumes + *auto_attribs=True*. + 2. Otherwise it assumes *auto_attribs=False* and tries to collect + `attr.ib`\ s. + + and that mutable classes (``frozen=False``) validate on ``__setattr__``. + + .. versionadded:: 20.1.0 + """ + + def do_it(cls, auto_attribs): + return attrs( + maybe_cls=cls, + these=these, + repr=repr, + hash=hash, + init=init, + slots=slots, + frozen=frozen, + weakref_slot=weakref_slot, + str=str, + auto_attribs=auto_attribs, + kw_only=kw_only, + cache_hash=cache_hash, + auto_exc=auto_exc, + eq=eq, + order=order, + auto_detect=auto_detect, + collect_by_mro=True, + getstate_setstate=getstate_setstate, + on_setattr=on_setattr, + field_transformer=field_transformer, + ) + + def wrap(cls): + """ + Making this a wrapper ensures this code runs during class creation. + + We also ensure that frozen-ness of classes is inherited. + """ + nonlocal frozen, on_setattr + + had_on_setattr = on_setattr not in (None, setters.NO_OP) + + # By default, mutable classes validate on setattr. + if frozen is False and on_setattr is None: + on_setattr = setters.validate + + # However, if we subclass a frozen class, we inherit the immutability + # and disable on_setattr. + for base_cls in cls.__bases__: + if base_cls.__setattr__ is _frozen_setattrs: + if had_on_setattr: + raise ValueError( + "Frozen classes can't use on_setattr " + "(frozen-ness was inherited)." + ) + + on_setattr = setters.NO_OP + break + + if auto_attribs is not None: + return do_it(cls, auto_attribs) + + try: + return do_it(cls, True) + except UnannotatedAttributeError: + return do_it(cls, False) + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but ``None`` if used as `@attrs()`. + if maybe_cls is None: + return wrap + else: + return wrap(maybe_cls) + + +mutable = define +frozen = partial(define, frozen=True, on_setattr=None) + + +def field( + *, + default=NOTHING, + validator=None, + repr=True, + hash=None, + init=True, + metadata=None, + converter=None, + factory=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, +): + """ + Identical to `attr.ib`, except keyword-only and with some arguments + removed. + + .. versionadded:: 20.1.0 + """ + return attrib( + default=default, + validator=validator, + repr=repr, + hash=hash, + init=init, + metadata=metadata, + converter=converter, + factory=factory, + kw_only=kw_only, + eq=eq, + order=order, + on_setattr=on_setattr, + ) diff --git a/robot/lib/python3.8/site-packages/attr/_version_info.py b/robot/lib/python3.8/site-packages/attr/_version_info.py new file mode 100644 index 0000000000000000000000000000000000000000..014e78a1b43851a2795d5b0e45cfde708e577460 --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/_version_info.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import, division, print_function + +from functools import total_ordering + +from ._funcs import astuple +from ._make import attrib, attrs + + +@total_ordering +@attrs(eq=False, order=False, slots=True, frozen=True) +class VersionInfo(object): + """ + A version object that can be compared to tuple of length 1--4: + + >>> attr.VersionInfo(19, 1, 0, "final") <= (19, 2) + True + >>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1) + True + >>> vi = attr.VersionInfo(19, 2, 0, "final") + >>> vi < (19, 1, 1) + False + >>> vi < (19,) + False + >>> vi == (19, 2,) + True + >>> vi == (19, 2, 1) + False + + .. versionadded:: 19.2 + """ + + year = attrib(type=int) + minor = attrib(type=int) + micro = attrib(type=int) + releaselevel = attrib(type=str) + + @classmethod + def _from_version_string(cls, s): + """ + Parse *s* and return a _VersionInfo. + """ + v = s.split(".") + if len(v) == 3: + v.append("final") + + return cls( + year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3] + ) + + def _ensure_tuple(self, other): + """ + Ensure *other* is a tuple of a valid length. + + Returns a possibly transformed *other* and ourselves as a tuple of + the same length as *other*. + """ + + if self.__class__ is other.__class__: + other = astuple(other) + + if not isinstance(other, tuple): + raise NotImplementedError + + if not (1 <= len(other) <= 4): + raise NotImplementedError + + return astuple(self)[: len(other)], other + + def __eq__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + return us == them + + def __lt__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + # Since alphabetically "dev0" < "final" < "post1" < "post2", we don't + # have to do anything special with releaselevel for now. + return us < them diff --git a/robot/lib/python3.8/site-packages/attr/_version_info.pyi b/robot/lib/python3.8/site-packages/attr/_version_info.pyi new file mode 100644 index 0000000000000000000000000000000000000000..45ced086337783c4b73b26cd17d2c1c260e24029 --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/_version_info.pyi @@ -0,0 +1,9 @@ +class VersionInfo: + @property + def year(self) -> int: ... + @property + def minor(self) -> int: ... + @property + def micro(self) -> int: ... + @property + def releaselevel(self) -> str: ... diff --git a/robot/lib/python3.8/site-packages/attr/converters.py b/robot/lib/python3.8/site-packages/attr/converters.py new file mode 100644 index 0000000000000000000000000000000000000000..715ce17859f456e7197d8d3b09ec4ecea8b004d1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/converters.py @@ -0,0 +1,85 @@ +""" +Commonly useful converters. +""" + +from __future__ import absolute_import, division, print_function + +from ._make import NOTHING, Factory, pipe + + +__all__ = [ + "pipe", + "optional", + "default_if_none", +] + + +def optional(converter): + """ + A converter that allows an attribute to be optional. An optional attribute + is one which can be set to ``None``. + + :param callable converter: the converter that is used for non-``None`` + values. + + .. versionadded:: 17.1.0 + """ + + def optional_converter(val): + if val is None: + return None + return converter(val) + + return optional_converter + + +def default_if_none(default=NOTHING, factory=None): + """ + A converter that allows to replace ``None`` values by *default* or the + result of *factory*. + + :param default: Value to be used if ``None`` is passed. Passing an instance + of `attr.Factory` is supported, however the ``takes_self`` option + is *not*. + :param callable factory: A callable that takes not parameters whose result + is used if ``None`` is passed. + + :raises TypeError: If **neither** *default* or *factory* is passed. + :raises TypeError: If **both** *default* and *factory* are passed. + :raises ValueError: If an instance of `attr.Factory` is passed with + ``takes_self=True``. + + .. versionadded:: 18.2.0 + """ + if default is NOTHING and factory is None: + raise TypeError("Must pass either `default` or `factory`.") + + if default is not NOTHING and factory is not None: + raise TypeError( + "Must pass either `default` or `factory` but not both." + ) + + if factory is not None: + default = Factory(factory) + + if isinstance(default, Factory): + if default.takes_self: + raise ValueError( + "`takes_self` is not supported by default_if_none." + ) + + def default_if_none_converter(val): + if val is not None: + return val + + return default.factory() + + else: + + def default_if_none_converter(val): + if val is not None: + return val + + return default + + return default_if_none_converter diff --git a/robot/lib/python3.8/site-packages/attr/converters.pyi b/robot/lib/python3.8/site-packages/attr/converters.pyi new file mode 100644 index 0000000000000000000000000000000000000000..7b0caa14f0c1d2224744a6a5c3650004082fadcf --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/converters.pyi @@ -0,0 +1,11 @@ +from typing import TypeVar, Optional, Callable, overload +from . import _ConverterType + +_T = TypeVar("_T") + +def pipe(*validators: _ConverterType) -> _ConverterType: ... +def optional(converter: _ConverterType) -> _ConverterType: ... +@overload +def default_if_none(default: _T) -> _ConverterType: ... +@overload +def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType: ... diff --git a/robot/lib/python3.8/site-packages/attr/exceptions.py b/robot/lib/python3.8/site-packages/attr/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..fcd89106f16fed3fbb84fc2f45b5327ecd5848cb --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/exceptions.py @@ -0,0 +1,92 @@ +from __future__ import absolute_import, division, print_function + + +class FrozenError(AttributeError): + """ + A frozen/immutable instance or attribute haave been attempted to be + modified. + + It mirrors the behavior of ``namedtuples`` by using the same error message + and subclassing `AttributeError`. + + .. versionadded:: 20.1.0 + """ + + msg = "can't set attribute" + args = [msg] + + +class FrozenInstanceError(FrozenError): + """ + A frozen instance has been attempted to be modified. + + .. versionadded:: 16.1.0 + """ + + +class FrozenAttributeError(FrozenError): + """ + A frozen attribute has been attempted to be modified. + + .. versionadded:: 20.1.0 + """ + + +class AttrsAttributeNotFoundError(ValueError): + """ + An ``attrs`` function couldn't find an attribute that the user asked for. + + .. versionadded:: 16.2.0 + """ + + +class NotAnAttrsClassError(ValueError): + """ + A non-``attrs`` class has been passed into an ``attrs`` function. + + .. versionadded:: 16.2.0 + """ + + +class DefaultAlreadySetError(RuntimeError): + """ + A default has been set using ``attr.ib()`` and is attempted to be reset + using the decorator. + + .. versionadded:: 17.1.0 + """ + + +class UnannotatedAttributeError(RuntimeError): + """ + A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type + annotation. + + .. versionadded:: 17.3.0 + """ + + +class PythonTooOldError(RuntimeError): + """ + It was attempted to use an ``attrs`` feature that requires a newer Python + version. + + .. versionadded:: 18.2.0 + """ + + +class NotCallableError(TypeError): + """ + A ``attr.ib()`` requiring a callable has been set with a value + that is not callable. + + .. versionadded:: 19.2.0 + """ + + def __init__(self, msg, value): + super(TypeError, self).__init__(msg, value) + self.msg = msg + self.value = value + + def __str__(self): + return str(self.msg) diff --git a/robot/lib/python3.8/site-packages/attr/exceptions.pyi b/robot/lib/python3.8/site-packages/attr/exceptions.pyi new file mode 100644 index 0000000000000000000000000000000000000000..f2680118b404db8f5227d04d27e8439331341c4d --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/exceptions.pyi @@ -0,0 +1,17 @@ +from typing import Any + +class FrozenError(AttributeError): + msg: str = ... + +class FrozenInstanceError(FrozenError): ... +class FrozenAttributeError(FrozenError): ... +class AttrsAttributeNotFoundError(ValueError): ... +class NotAnAttrsClassError(ValueError): ... +class DefaultAlreadySetError(RuntimeError): ... +class UnannotatedAttributeError(RuntimeError): ... +class PythonTooOldError(RuntimeError): ... + +class NotCallableError(TypeError): + msg: str = ... + value: Any = ... + def __init__(self, msg: str, value: Any) -> None: ... diff --git a/robot/lib/python3.8/site-packages/attr/filters.py b/robot/lib/python3.8/site-packages/attr/filters.py new file mode 100644 index 0000000000000000000000000000000000000000..dc47e8fa38ce6af08ae29125dc97ee2b19e948d2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/filters.py @@ -0,0 +1,52 @@ +""" +Commonly useful filters for `attr.asdict`. +""" + +from __future__ import absolute_import, division, print_function + +from ._compat import isclass +from ._make import Attribute + + +def _split_what(what): + """ + Returns a tuple of `frozenset`s of classes and attributes. + """ + return ( + frozenset(cls for cls in what if isclass(cls)), + frozenset(cls for cls in what if isinstance(cls, Attribute)), + ) + + +def include(*what): + """ + Whitelist *what*. + + :param what: What to whitelist. + :type what: `list` of `type` or `attr.Attribute`\\ s + + :rtype: `callable` + """ + cls, attrs = _split_what(what) + + def include_(attribute, value): + return value.__class__ in cls or attribute in attrs + + return include_ + + +def exclude(*what): + """ + Blacklist *what*. + + :param what: What to blacklist. + :type what: `list` of classes or `attr.Attribute`\\ s. + + :rtype: `callable` + """ + cls, attrs = _split_what(what) + + def exclude_(attribute, value): + return value.__class__ not in cls and attribute not in attrs + + return exclude_ diff --git a/robot/lib/python3.8/site-packages/attr/filters.pyi b/robot/lib/python3.8/site-packages/attr/filters.pyi new file mode 100644 index 0000000000000000000000000000000000000000..68368fe2b92d8f61ef4ca176d007975af08c3cca --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/filters.pyi @@ -0,0 +1,5 @@ +from typing import Union, Any +from . import Attribute, _FilterType + +def include(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ... +def exclude(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ... diff --git a/robot/lib/python3.8/site-packages/attr/py.typed b/robot/lib/python3.8/site-packages/attr/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/attr/setters.py b/robot/lib/python3.8/site-packages/attr/setters.py new file mode 100644 index 0000000000000000000000000000000000000000..240014b3c1e4eb2f264ca6cabe0a426da2eb7aac --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/setters.py @@ -0,0 +1,77 @@ +""" +Commonly used hooks for on_setattr. +""" + +from __future__ import absolute_import, division, print_function + +from . import _config +from .exceptions import FrozenAttributeError + + +def pipe(*setters): + """ + Run all *setters* and return the return value of the last one. + + .. versionadded:: 20.1.0 + """ + + def wrapped_pipe(instance, attrib, new_value): + rv = new_value + + for setter in setters: + rv = setter(instance, attrib, rv) + + return rv + + return wrapped_pipe + + +def frozen(_, __, ___): + """ + Prevent an attribute to be modified. + + .. versionadded:: 20.1.0 + """ + raise FrozenAttributeError() + + +def validate(instance, attrib, new_value): + """ + Run *attrib*'s validator on *new_value* if it has one. + + .. versionadded:: 20.1.0 + """ + if _config._run_validators is False: + return new_value + + v = attrib.validator + if not v: + return new_value + + v(instance, attrib, new_value) + + return new_value + + +def convert(instance, attrib, new_value): + """ + Run *attrib*'s converter -- if it has one -- on *new_value* and return the + result. + + .. versionadded:: 20.1.0 + """ + c = attrib.converter + if c: + return c(new_value) + + return new_value + + +NO_OP = object() +""" +Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. + +Does not work in `pipe` or within lists. + +.. versionadded:: 20.1.0 +""" diff --git a/robot/lib/python3.8/site-packages/attr/setters.pyi b/robot/lib/python3.8/site-packages/attr/setters.pyi new file mode 100644 index 0000000000000000000000000000000000000000..19bc33fd1e27057f80f89125607719a296158f24 --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/setters.pyi @@ -0,0 +1,18 @@ +from . import _OnSetAttrType, Attribute +from typing import TypeVar, Any, NewType, NoReturn, cast + +_T = TypeVar("_T") + +def frozen( + instance: Any, attribute: Attribute, new_value: Any +) -> NoReturn: ... +def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ... +def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ... + +# convert is allowed to return Any, because they can be chained using pipe. +def convert( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> Any: ... + +_NoOpType = NewType("_NoOpType", object) +NO_OP: _NoOpType diff --git a/robot/lib/python3.8/site-packages/attr/validators.py b/robot/lib/python3.8/site-packages/attr/validators.py new file mode 100644 index 0000000000000000000000000000000000000000..b9a73054e9c302fc28d455b95368abf539a3196a --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/validators.py @@ -0,0 +1,379 @@ +""" +Commonly useful validators. +""" + +from __future__ import absolute_import, division, print_function + +import re + +from ._make import _AndValidator, and_, attrib, attrs +from .exceptions import NotCallableError + + +__all__ = [ + "and_", + "deep_iterable", + "deep_mapping", + "in_", + "instance_of", + "is_callable", + "matches_re", + "optional", + "provides", +] + + +@attrs(repr=False, slots=True, hash=True) +class _InstanceOfValidator(object): + type = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not isinstance(value, self.type): + raise TypeError( + "'{name}' must be {type!r} (got {value!r} that is a " + "{actual!r}).".format( + name=attr.name, + type=self.type, + actual=value.__class__, + value=value, + ), + attr, + self.type, + value, + ) + + def __repr__(self): + return "".format( + type=self.type + ) + + +def instance_of(type): + """ + A validator that raises a `TypeError` if the initializer is called + with a wrong type for this particular attribute (checks are performed using + `isinstance` therefore it's also valid to pass a tuple of types). + + :param type: The type to check for. + :type type: type or tuple of types + + :raises TypeError: With a human readable error message, the attribute + (of type `attr.Attribute`), the expected type, and the value it + got. + """ + return _InstanceOfValidator(type) + + +@attrs(repr=False, frozen=True, slots=True) +class _MatchesReValidator(object): + regex = attrib() + flags = attrib() + match_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.match_func(value): + raise ValueError( + "'{name}' must match regex {regex!r}" + " ({value!r} doesn't)".format( + name=attr.name, regex=self.regex.pattern, value=value + ), + attr, + self.regex, + value, + ) + + def __repr__(self): + return "".format( + regex=self.regex + ) + + +def matches_re(regex, flags=0, func=None): + r""" + A validator that raises `ValueError` if the initializer is called + with a string that doesn't match *regex*. + + :param str regex: a regex string to match against + :param int flags: flags that will be passed to the underlying re function + (default 0) + :param callable func: which underlying `re` function to call (options + are `re.fullmatch`, `re.search`, `re.match`, default + is ``None`` which means either `re.fullmatch` or an emulation of + it on Python 2). For performance reasons, they won't be used directly + but on a pre-`re.compile`\ ed pattern. + + .. versionadded:: 19.2.0 + """ + fullmatch = getattr(re, "fullmatch", None) + valid_funcs = (fullmatch, None, re.search, re.match) + if func not in valid_funcs: + raise ValueError( + "'func' must be one of %s." + % ( + ", ".join( + sorted( + e and e.__name__ or "None" for e in set(valid_funcs) + ) + ), + ) + ) + + pattern = re.compile(regex, flags) + if func is re.match: + match_func = pattern.match + elif func is re.search: + match_func = pattern.search + else: + if fullmatch: + match_func = pattern.fullmatch + else: + pattern = re.compile(r"(?:{})\Z".format(regex), flags) + match_func = pattern.match + + return _MatchesReValidator(pattern, flags, match_func) + + +@attrs(repr=False, slots=True, hash=True) +class _ProvidesValidator(object): + interface = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.interface.providedBy(value): + raise TypeError( + "'{name}' must provide {interface!r} which {value!r} " + "doesn't.".format( + name=attr.name, interface=self.interface, value=value + ), + attr, + self.interface, + value, + ) + + def __repr__(self): + return "".format( + interface=self.interface + ) + + +def provides(interface): + """ + A validator that raises a `TypeError` if the initializer is called + with an object that does not provide the requested *interface* (checks are + performed using ``interface.providedBy(value)`` (see `zope.interface + `_). + + :param interface: The interface to check for. + :type interface: ``zope.interface.Interface`` + + :raises TypeError: With a human readable error message, the attribute + (of type `attr.Attribute`), the expected interface, and the + value it got. + """ + return _ProvidesValidator(interface) + + +@attrs(repr=False, slots=True, hash=True) +class _OptionalValidator(object): + validator = attrib() + + def __call__(self, inst, attr, value): + if value is None: + return + + self.validator(inst, attr, value) + + def __repr__(self): + return "".format( + what=repr(self.validator) + ) + + +def optional(validator): + """ + A validator that makes an attribute optional. An optional attribute is one + which can be set to ``None`` in addition to satisfying the requirements of + the sub-validator. + + :param validator: A validator (or a list of validators) that is used for + non-``None`` values. + :type validator: callable or `list` of callables. + + .. versionadded:: 15.1.0 + .. versionchanged:: 17.1.0 *validator* can be a list of validators. + """ + if isinstance(validator, list): + return _OptionalValidator(_AndValidator(validator)) + return _OptionalValidator(validator) + + +@attrs(repr=False, slots=True, hash=True) +class _InValidator(object): + options = attrib() + + def __call__(self, inst, attr, value): + try: + in_options = value in self.options + except TypeError: # e.g. `1 in "abc"` + in_options = False + + if not in_options: + raise ValueError( + "'{name}' must be in {options!r} (got {value!r})".format( + name=attr.name, options=self.options, value=value + ) + ) + + def __repr__(self): + return "".format( + options=self.options + ) + + +def in_(options): + """ + A validator that raises a `ValueError` if the initializer is called + with a value that does not belong in the options provided. The check is + performed using ``value in options``. + + :param options: Allowed options. + :type options: list, tuple, `enum.Enum`, ... + + :raises ValueError: With a human readable error message, the attribute (of + type `attr.Attribute`), the expected options, and the value it + got. + + .. versionadded:: 17.1.0 + """ + return _InValidator(options) + + +@attrs(repr=False, slots=False, hash=True) +class _IsCallableValidator(object): + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not callable(value): + message = ( + "'{name}' must be callable " + "(got {value!r} that is a {actual!r})." + ) + raise NotCallableError( + msg=message.format( + name=attr.name, value=value, actual=value.__class__ + ), + value=value, + ) + + def __repr__(self): + return "" + + +def is_callable(): + """ + A validator that raises a `attr.exceptions.NotCallableError` if the + initializer is called with a value for this particular attribute + that is not callable. + + .. versionadded:: 19.1.0 + + :raises `attr.exceptions.NotCallableError`: With a human readable error + message containing the attribute (`attr.Attribute`) name, + and the value it got. + """ + return _IsCallableValidator() + + +@attrs(repr=False, slots=True, hash=True) +class _DeepIterable(object): + member_validator = attrib(validator=is_callable()) + iterable_validator = attrib( + default=None, validator=optional(is_callable()) + ) + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if self.iterable_validator is not None: + self.iterable_validator(inst, attr, value) + + for member in value: + self.member_validator(inst, attr, member) + + def __repr__(self): + iterable_identifier = ( + "" + if self.iterable_validator is None + else " {iterable!r}".format(iterable=self.iterable_validator) + ) + return ( + "" + ).format( + iterable_identifier=iterable_identifier, + member=self.member_validator, + ) + + +def deep_iterable(member_validator, iterable_validator=None): + """ + A validator that performs deep validation of an iterable. + + :param member_validator: Validator to apply to iterable members + :param iterable_validator: Validator to apply to iterable itself + (optional) + + .. versionadded:: 19.1.0 + + :raises TypeError: if any sub-validators fail + """ + return _DeepIterable(member_validator, iterable_validator) + + +@attrs(repr=False, slots=True, hash=True) +class _DeepMapping(object): + key_validator = attrib(validator=is_callable()) + value_validator = attrib(validator=is_callable()) + mapping_validator = attrib(default=None, validator=optional(is_callable())) + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if self.mapping_validator is not None: + self.mapping_validator(inst, attr, value) + + for key in value: + self.key_validator(inst, attr, key) + self.value_validator(inst, attr, value[key]) + + def __repr__(self): + return ( + "" + ).format(key=self.key_validator, value=self.value_validator) + + +def deep_mapping(key_validator, value_validator, mapping_validator=None): + """ + A validator that performs deep validation of a dictionary. + + :param key_validator: Validator to apply to dictionary keys + :param value_validator: Validator to apply to dictionary values + :param mapping_validator: Validator to apply to top-level mapping + attribute (optional) + + .. versionadded:: 19.1.0 + + :raises TypeError: if any sub-validators fail + """ + return _DeepMapping(key_validator, value_validator, mapping_validator) diff --git a/robot/lib/python3.8/site-packages/attr/validators.pyi b/robot/lib/python3.8/site-packages/attr/validators.pyi new file mode 100644 index 0000000000000000000000000000000000000000..9a22abb197d7b2da7402d8f05cebee4186f9e15f --- /dev/null +++ b/robot/lib/python3.8/site-packages/attr/validators.pyi @@ -0,0 +1,66 @@ +from typing import ( + Container, + List, + Union, + TypeVar, + Type, + Any, + Optional, + Tuple, + Iterable, + Mapping, + Callable, + Match, + AnyStr, + overload, +) +from . import _ValidatorType + +_T = TypeVar("_T") +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") +_T3 = TypeVar("_T3") +_I = TypeVar("_I", bound=Iterable) +_K = TypeVar("_K") +_V = TypeVar("_V") +_M = TypeVar("_M", bound=Mapping) + +# To be more precise on instance_of use some overloads. +# If there are more than 3 items in the tuple then we fall back to Any +@overload +def instance_of(type: Type[_T]) -> _ValidatorType[_T]: ... +@overload +def instance_of(type: Tuple[Type[_T]]) -> _ValidatorType[_T]: ... +@overload +def instance_of( + type: Tuple[Type[_T1], Type[_T2]] +) -> _ValidatorType[Union[_T1, _T2]]: ... +@overload +def instance_of( + type: Tuple[Type[_T1], Type[_T2], Type[_T3]] +) -> _ValidatorType[Union[_T1, _T2, _T3]]: ... +@overload +def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ... +def provides(interface: Any) -> _ValidatorType[Any]: ... +def optional( + validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]] +) -> _ValidatorType[Optional[_T]]: ... +def in_(options: Container[_T]) -> _ValidatorType[_T]: ... +def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... +def matches_re( + regex: AnyStr, + flags: int = ..., + func: Optional[ + Callable[[AnyStr, AnyStr, int], Optional[Match[AnyStr]]] + ] = ..., +) -> _ValidatorType[AnyStr]: ... +def deep_iterable( + member_validator: _ValidatorType[_T], + iterable_validator: Optional[_ValidatorType[_I]] = ..., +) -> _ValidatorType[_I]: ... +def deep_mapping( + key_validator: _ValidatorType[_K], + value_validator: _ValidatorType[_V], + mapping_validator: Optional[_ValidatorType[_M]] = ..., +) -> _ValidatorType[_M]: ... +def is_callable() -> _ValidatorType[_T]: ... diff --git a/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/AUTHORS.rst b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/AUTHORS.rst new file mode 100644 index 0000000000000000000000000000000000000000..f14ef6c60749458fcb60bfdee27efe4d957b4aeb --- /dev/null +++ b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/AUTHORS.rst @@ -0,0 +1,11 @@ +Credits +======= + +``attrs`` is written and maintained by `Hynek Schlawack `_. + +The development is kindly supported by `Variomedia AG `_. + +A full list of contributors can be found in `GitHub's overview `_. + +It’s the spiritual successor of `characteristic `_ and aspires to fix some of it clunkiness and unfortunate decisions. +Both were inspired by Twisted’s `FancyEqMixin `_ but both are implemented using class decorators because `subclassing is bad for you `_, m’kay? diff --git a/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/LICENSE b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..7ae3df930976bd01d34041b1c7ceeb4b32aace8c --- /dev/null +++ b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Hynek Schlawack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/METADATA b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..a92cacb0072abd9fb4d805967ef1fb7e1b14ba01 --- /dev/null +++ b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/METADATA @@ -0,0 +1,241 @@ +Metadata-Version: 2.1 +Name: attrs +Version: 20.3.0 +Summary: Classes Without Boilerplate +Home-page: https://www.attrs.org/ +Author: Hynek Schlawack +Author-email: hs@ox.cx +Maintainer: Hynek Schlawack +Maintainer-email: hs@ox.cx +License: MIT +Project-URL: Documentation, https://www.attrs.org/ +Project-URL: Bug Tracker, https://github.com/python-attrs/attrs/issues +Project-URL: Source Code, https://github.com/python-attrs/attrs +Project-URL: Funding, https://github.com/sponsors/hynek +Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=pypi +Keywords: class,attribute,boilerplate +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +Description-Content-Type: text/x-rst +Provides-Extra: dev +Requires-Dist: coverage[toml] (>=5.0.2) ; extra == 'dev' +Requires-Dist: hypothesis ; extra == 'dev' +Requires-Dist: pympler ; extra == 'dev' +Requires-Dist: pytest (>=4.3.0) ; extra == 'dev' +Requires-Dist: six ; extra == 'dev' +Requires-Dist: zope.interface ; extra == 'dev' +Requires-Dist: furo ; extra == 'dev' +Requires-Dist: sphinx ; extra == 'dev' +Requires-Dist: pre-commit ; extra == 'dev' +Provides-Extra: docs +Requires-Dist: furo ; extra == 'docs' +Requires-Dist: sphinx ; extra == 'docs' +Requires-Dist: zope.interface ; extra == 'docs' +Provides-Extra: tests +Requires-Dist: coverage[toml] (>=5.0.2) ; extra == 'tests' +Requires-Dist: hypothesis ; extra == 'tests' +Requires-Dist: pympler ; extra == 'tests' +Requires-Dist: pytest (>=4.3.0) ; extra == 'tests' +Requires-Dist: six ; extra == 'tests' +Requires-Dist: zope.interface ; extra == 'tests' +Provides-Extra: tests_no_zope +Requires-Dist: coverage[toml] (>=5.0.2) ; extra == 'tests_no_zope' +Requires-Dist: hypothesis ; extra == 'tests_no_zope' +Requires-Dist: pympler ; extra == 'tests_no_zope' +Requires-Dist: pytest (>=4.3.0) ; extra == 'tests_no_zope' +Requires-Dist: six ; extra == 'tests_no_zope' + +.. image:: https://www.attrs.org/en/latest/_static/attrs_logo.png + :alt: attrs Logo + +====================================== +``attrs``: Classes Without Boilerplate +====================================== + +.. image:: https://readthedocs.org/projects/attrs/badge/?version=stable + :target: https://www.attrs.org/en/stable/?badge=stable + :alt: Documentation Status + +.. image:: https://github.com/python-attrs/attrs/workflows/CI/badge.svg?branch=master + :target: https://github.com/python-attrs/attrs/actions?workflow=CI + :alt: CI Status + +.. image:: https://codecov.io/github/python-attrs/attrs/branch/master/graph/badge.svg + :target: https://codecov.io/github/python-attrs/attrs + :alt: Test Coverage + +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: black + +.. teaser-begin + +``attrs`` is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka `dunder `_ methods). + +Its main goal is to help you to write **concise** and **correct** software without slowing down your code. + +.. teaser-end + +For that, it gives you a class decorator and a way to declaratively define the attributes on that class: + +.. -code-begin- + +.. code-block:: pycon + + >>> import attr + + >>> @attr.s + ... class SomeClass(object): + ... a_number = attr.ib(default=42) + ... list_of_numbers = attr.ib(factory=list) + ... + ... def hard_math(self, another_number): + ... return self.a_number + sum(self.list_of_numbers) * another_number + + + >>> sc = SomeClass(1, [1, 2, 3]) + >>> sc + SomeClass(a_number=1, list_of_numbers=[1, 2, 3]) + + >>> sc.hard_math(3) + 19 + >>> sc == SomeClass(1, [1, 2, 3]) + True + >>> sc != SomeClass(2, [3, 2, 1]) + True + + >>> attr.asdict(sc) + {'a_number': 1, 'list_of_numbers': [1, 2, 3]} + + >>> SomeClass() + SomeClass(a_number=42, list_of_numbers=[]) + + >>> C = attr.make_class("C", ["a", "b"]) + >>> C("foo", "bar") + C(a='foo', b='bar') + + +After *declaring* your attributes ``attrs`` gives you: + +- a concise and explicit overview of the class's attributes, +- a nice human-readable ``__repr__``, +- a complete set of comparison methods (equality and ordering), +- an initializer, +- and much more, + +*without* writing dull boilerplate code again and again and *without* runtime performance penalties. + +On Python 3.6 and later, you can often even drop the calls to ``attr.ib()`` by using `type annotations `_. + +This gives you the power to use actual classes with actual types in your code instead of confusing ``tuple``\ s or `confusingly behaving `_ ``namedtuple``\ s. +Which in turn encourages you to write *small classes* that do `one thing well `_. +Never again violate the `single responsibility principle `_ just because implementing ``__init__`` et al is a painful drag. + + +.. -getting-help- + +Getting Help +============ + +Please use the ``python-attrs`` tag on `StackOverflow `_ to get help. + +Answering questions of your fellow developers is also a great way to help the project! + + +.. -project-information- + +Project Information +=================== + +``attrs`` is released under the `MIT `_ license, +its documentation lives at `Read the Docs `_, +the code on `GitHub `_, +and the latest release on `PyPI `_. +It’s rigorously tested on Python 2.7, 3.5+, and PyPy. + +We collect information on **third-party extensions** in our `wiki `_. +Feel free to browse and add your own! + +If you'd like to contribute to ``attrs`` you're most welcome and we've written `a little guide `_ to get you started! + + +``attrs`` for Enterprise +------------------------ + +Available as part of the Tidelift Subscription. + +The maintainers of ``attrs`` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. +Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. +`Learn more. `_ + + +Release Information +=================== + +20.3.0 (2020-11-05) +------------------- + +Backward-incompatible Changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- ``attr.define()``, ``attr.frozen()``, ``attr.mutable()``, and ``attr.field()`` remain **provisional**. + + This release does **not** change change anything about them and they are already used widely in production though. + + If you wish to use them together with mypy, you can simply drop `this plugin `_ into your project. + + Feel free to provide feedback to them in the linked issue #668. + + We will release the ``attrs`` namespace once we have the feeling that the APIs have properly settled. + `#668 `_ + + +Changes +^^^^^^^ + +- ``attr.s()`` now has a *field_transformer* hook that is called for all ``Attribute``\ s and returns a (modified or updated) list of ``Attribute`` instances. + ``attr.asdict()`` has a *value_serializer* hook that can change the way values are converted. + Both hooks are meant to help with data (de-)serialization workflows. + `#653 `_ +- ``kw_only=True`` now works on Python 2. + `#700 `_ +- ``raise from`` now works on frozen classes on PyPy. + `#703 `_, + `#712 `_ +- ``attr.asdict()`` and ``attr.astuple()`` now treat ``frozenset``\ s like ``set``\ s with regards to the *retain_collection_types* argument. + `#704 `_ +- The type stubs for ``attr.s()`` and ``attr.make_class()`` are not missing the *collect_by_mro* argument anymore. + `#711 `_ + +`Full changelog `_. + +Credits +======= + +``attrs`` is written and maintained by `Hynek Schlawack `_. + +The development is kindly supported by `Variomedia AG `_. + +A full list of contributors can be found in `GitHub's overview `_. + +It’s the spiritual successor of `characteristic `_ and aspires to fix some of it clunkiness and unfortunate decisions. +Both were inspired by Twisted’s `FancyEqMixin `_ but both are implemented using class decorators because `subclassing is bad for you `_, m’kay? + + diff --git a/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/RECORD b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..2a564f9774da22e0015a8329ceb875fa359311eb --- /dev/null +++ b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/RECORD @@ -0,0 +1,39 @@ +attr/__init__.py,sha256=70KmZOgz2sUvtRTC_IuXEeN2ttOyBWHn4XA59aqGXPs,1568 +attr/__init__.pyi,sha256=ca_4sg7z0e_EL7ehy-flXVGAju5PBX2hVo51dUmPMi0,12986 +attr/__pycache__/__init__.cpython-38.pyc,, +attr/__pycache__/_compat.cpython-38.pyc,, +attr/__pycache__/_config.cpython-38.pyc,, +attr/__pycache__/_funcs.cpython-38.pyc,, +attr/__pycache__/_make.cpython-38.pyc,, +attr/__pycache__/_next_gen.cpython-38.pyc,, +attr/__pycache__/_version_info.cpython-38.pyc,, +attr/__pycache__/converters.cpython-38.pyc,, +attr/__pycache__/exceptions.cpython-38.pyc,, +attr/__pycache__/filters.cpython-38.pyc,, +attr/__pycache__/setters.cpython-38.pyc,, +attr/__pycache__/validators.cpython-38.pyc,, +attr/_compat.py,sha256=rZhpP09xbyWSzMv796XQbryIr21oReJFvA70G3lrHxg,7308 +attr/_config.py,sha256=_KvW0mQdH2PYjHc0YfIUaV_o2pVfM7ziMEYTxwmEhOA,514 +attr/_funcs.py,sha256=PvFQlflEswO_qIR2sUr4a4x8ggQpEoDKe3YKM2rLJu4,13081 +attr/_make.py,sha256=61XB4-SHQpFbWbStGWotTTbzVT2m49DUovRgnxpMqmU,88313 +attr/_next_gen.py,sha256=x6TU2rVOXmFmrNNvkfshJsxyRbAAK0wDI4SJV2OI97c,4138 +attr/_version_info.py,sha256=azMi1lNelb3cJvvYUMXsXVbUANkRzbD5IEiaXVpeVr4,2162 +attr/_version_info.pyi,sha256=x_M3L3WuB7r_ULXAWjx959udKQ4HLB8l-hsc1FDGNvk,209 +attr/converters.py,sha256=CaK6iLtEMmemrqU8LQ1D2nWtbo9dGPAv4UaZ0rFzhOA,2214 +attr/converters.pyi,sha256=fVGSfawF3NMy2EBApkC7dAwMuujWCHnGEnnAgsbkVpg,380 +attr/exceptions.py,sha256=gmlET97ikqdQVvy7Ff9p7zVvqc2SsNtTd-r30pva1GE,1950 +attr/exceptions.pyi,sha256=zZq8bCUnKAy9mDtBEw42ZhPhAUIHoTKedDQInJD883M,539 +attr/filters.py,sha256=weDxwATsa69T_0bPVjiM1fGsciAMQmwhY5G8Jm5BxuI,1098 +attr/filters.pyi,sha256=xDpmKQlFdssgxGa5tsl1ADh_3zwAwAT4vUhd8h-8-Tk,214 +attr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +attr/setters.py,sha256=0ElzHwdVK3dsYcQi2CXkFvhx8fNxUI5OVhw8SWeaKmA,1434 +attr/setters.pyi,sha256=SYr6adhx4f0dSkmmBICg6eK8WMev5jT-KJQJTdul078,567 +attr/validators.py,sha256=6DBx1jt4oZxx1ppvx6JWqm9-UAsYpXC4HTwxJilCeRg,11497 +attr/validators.pyi,sha256=vZgsJqUwrJevh4v_Hd7_RSXqDrBctE6-3AEZ7uYKodo,1868 +attrs-20.3.0.dist-info/AUTHORS.rst,sha256=wsqCNbGz_mklcJrt54APIZHZpoTIJLkXqEhhn4Nd8hc,752 +attrs-20.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +attrs-20.3.0.dist-info/LICENSE,sha256=v2WaKLSSQGAvVrvfSQy-LsUJsVuY-Z17GaUsdA4yeGM,1082 +attrs-20.3.0.dist-info/METADATA,sha256=2XTmALrRRbIZj9J8pJgpKYnyATu_NAL8vfUnqRFpE5w,10220 +attrs-20.3.0.dist-info/RECORD,, +attrs-20.3.0.dist-info/WHEEL,sha256=ADKeyaGyKF5DwBNE0sRE5pvW-bSkFMJfBuhzZ3rceP4,110 +attrs-20.3.0.dist-info/top_level.txt,sha256=tlRYMddkRlKPqJ96wP2_j9uEsmcNHgD2SbuWd4CzGVU,5 diff --git a/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/WHEEL b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..6d38aa0601b31c7f4c47ff3016173426df4e1d53 --- /dev/null +++ b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.35.1) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..66a062d889dea72f909b74ddc9e576cc68393fd6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/attrs-20.3.0.dist-info/top_level.txt @@ -0,0 +1 @@ +attr diff --git a/robot/lib/python3.8/site-packages/cachecontrol/__init__.py b/robot/lib/python3.8/site-packages/cachecontrol/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a1bbbbe3bff592b2bc761772dc6974af4afb7035 --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/__init__.py @@ -0,0 +1,11 @@ +"""CacheControl import Interface. + +Make it easy to import from cachecontrol without long namespaces. +""" +__author__ = "Eric Larson" +__email__ = "eric@ionrock.org" +__version__ = "0.12.6" + +from .wrapper import CacheControl +from .adapter import CacheControlAdapter +from .controller import CacheController diff --git a/robot/lib/python3.8/site-packages/cachecontrol/_cmd.py b/robot/lib/python3.8/site-packages/cachecontrol/_cmd.py new file mode 100644 index 0000000000000000000000000000000000000000..ee8d60d101c6357c44c97421389d8d7b2ba63c09 --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/_cmd.py @@ -0,0 +1,57 @@ +import logging + +import requests + +from cachecontrol.adapter import CacheControlAdapter +from cachecontrol.cache import DictCache +from cachecontrol.controller import logger + +from argparse import ArgumentParser + + +def setup_logging(): + logger.setLevel(logging.DEBUG) + handler = logging.StreamHandler() + logger.addHandler(handler) + + +def get_session(): + adapter = CacheControlAdapter( + DictCache(), cache_etags=True, serializer=None, heuristic=None + ) + sess = requests.Session() + sess.mount("http://", adapter) + sess.mount("https://", adapter) + + sess.cache_controller = adapter.controller + return sess + + +def get_args(): + parser = ArgumentParser() + parser.add_argument("url", help="The URL to try and cache") + return parser.parse_args() + + +def main(args=None): + args = get_args() + sess = get_session() + + # Make a request to get a response + resp = sess.get(args.url) + + # Turn on logging + setup_logging() + + # try setting the cache + sess.cache_controller.cache_response(resp.request, resp.raw) + + # Now try to get it + if sess.cache_controller.cached_request(resp.request): + print("Cached!") + else: + print("Not cached :(") + + +if __name__ == "__main__": + main() diff --git a/robot/lib/python3.8/site-packages/cachecontrol/adapter.py b/robot/lib/python3.8/site-packages/cachecontrol/adapter.py new file mode 100644 index 0000000000000000000000000000000000000000..de50006af4b9533d16dcae5a18d39a336019286f --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/adapter.py @@ -0,0 +1,133 @@ +import types +import functools +import zlib + +from requests.adapters import HTTPAdapter + +from .controller import CacheController +from .cache import DictCache +from .filewrapper import CallbackFileWrapper + + +class CacheControlAdapter(HTTPAdapter): + invalidating_methods = {"PUT", "DELETE"} + + def __init__( + self, + cache=None, + cache_etags=True, + controller_class=None, + serializer=None, + heuristic=None, + cacheable_methods=None, + *args, + **kw + ): + super(CacheControlAdapter, self).__init__(*args, **kw) + self.cache = DictCache() if cache is None else cache + self.heuristic = heuristic + self.cacheable_methods = cacheable_methods or ("GET",) + + controller_factory = controller_class or CacheController + self.controller = controller_factory( + self.cache, cache_etags=cache_etags, serializer=serializer + ) + + def send(self, request, cacheable_methods=None, **kw): + """ + Send a request. Use the request information to see if it + exists in the cache and cache the response if we need to and can. + """ + cacheable = cacheable_methods or self.cacheable_methods + if request.method in cacheable: + try: + cached_response = self.controller.cached_request(request) + except zlib.error: + cached_response = None + if cached_response: + return self.build_response(request, cached_response, from_cache=True) + + # check for etags and add headers if appropriate + request.headers.update(self.controller.conditional_headers(request)) + + resp = super(CacheControlAdapter, self).send(request, **kw) + + return resp + + def build_response( + self, request, response, from_cache=False, cacheable_methods=None + ): + """ + Build a response by making a request or using the cache. + + This will end up calling send and returning a potentially + cached response + """ + cacheable = cacheable_methods or self.cacheable_methods + if not from_cache and request.method in cacheable: + # Check for any heuristics that might update headers + # before trying to cache. + if self.heuristic: + response = self.heuristic.apply(response) + + # apply any expiration heuristics + if response.status == 304: + # We must have sent an ETag request. This could mean + # that we've been expired already or that we simply + # have an etag. In either case, we want to try and + # update the cache if that is the case. + cached_response = self.controller.update_cached_response( + request, response + ) + + if cached_response is not response: + from_cache = True + + # We are done with the server response, read a + # possible response body (compliant servers will + # not return one, but we cannot be 100% sure) and + # release the connection back to the pool. + response.read(decode_content=False) + response.release_conn() + + response = cached_response + + # We always cache the 301 responses + elif response.status == 301: + self.controller.cache_response(request, response) + else: + # Wrap the response file with a wrapper that will cache the + # response when the stream has been consumed. + response._fp = CallbackFileWrapper( + response._fp, + functools.partial( + self.controller.cache_response, request, response + ), + ) + if response.chunked: + super_update_chunk_length = response._update_chunk_length + + def _update_chunk_length(self): + super_update_chunk_length() + if self.chunk_left == 0: + self._fp._close() + + response._update_chunk_length = types.MethodType( + _update_chunk_length, response + ) + + resp = super(CacheControlAdapter, self).build_response(request, response) + + # See if we should invalidate the cache. + if request.method in self.invalidating_methods and resp.ok: + cache_url = self.controller.cache_url(request.url) + self.cache.delete(cache_url) + + # Give the request a from_cache attr to let people use it + resp.from_cache = from_cache + + return resp + + def close(self): + self.cache.close() + super(CacheControlAdapter, self).close() diff --git a/robot/lib/python3.8/site-packages/cachecontrol/cache.py b/robot/lib/python3.8/site-packages/cachecontrol/cache.py new file mode 100644 index 0000000000000000000000000000000000000000..94e07732d91fdf38f4559da04db4221d26553d40 --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/cache.py @@ -0,0 +1,39 @@ +""" +The cache object API for implementing caches. The default is a thread +safe in-memory dictionary. +""" +from threading import Lock + + +class BaseCache(object): + + def get(self, key): + raise NotImplementedError() + + def set(self, key, value): + raise NotImplementedError() + + def delete(self, key): + raise NotImplementedError() + + def close(self): + pass + + +class DictCache(BaseCache): + + def __init__(self, init_dict=None): + self.lock = Lock() + self.data = init_dict or {} + + def get(self, key): + return self.data.get(key, None) + + def set(self, key, value): + with self.lock: + self.data.update({key: value}) + + def delete(self, key): + with self.lock: + if key in self.data: + self.data.pop(key) diff --git a/robot/lib/python3.8/site-packages/cachecontrol/caches/__init__.py b/robot/lib/python3.8/site-packages/cachecontrol/caches/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0e1658fa5e5e498bcfa84702ed1a53dfcec071ff --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/caches/__init__.py @@ -0,0 +1,2 @@ +from .file_cache import FileCache # noqa +from .redis_cache import RedisCache # noqa diff --git a/robot/lib/python3.8/site-packages/cachecontrol/caches/file_cache.py b/robot/lib/python3.8/site-packages/cachecontrol/caches/file_cache.py new file mode 100644 index 0000000000000000000000000000000000000000..607b9452428f4343a1a017dfd230b12456060543 --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/caches/file_cache.py @@ -0,0 +1,146 @@ +import hashlib +import os +from textwrap import dedent + +from ..cache import BaseCache +from ..controller import CacheController + +try: + FileNotFoundError +except NameError: + # py2.X + FileNotFoundError = (IOError, OSError) + + +def _secure_open_write(filename, fmode): + # We only want to write to this file, so open it in write only mode + flags = os.O_WRONLY + + # os.O_CREAT | os.O_EXCL will fail if the file already exists, so we only + # will open *new* files. + # We specify this because we want to ensure that the mode we pass is the + # mode of the file. + flags |= os.O_CREAT | os.O_EXCL + + # Do not follow symlinks to prevent someone from making a symlink that + # we follow and insecurely open a cache file. + if hasattr(os, "O_NOFOLLOW"): + flags |= os.O_NOFOLLOW + + # On Windows we'll mark this file as binary + if hasattr(os, "O_BINARY"): + flags |= os.O_BINARY + + # Before we open our file, we want to delete any existing file that is + # there + try: + os.remove(filename) + except (IOError, OSError): + # The file must not exist already, so we can just skip ahead to opening + pass + + # Open our file, the use of os.O_CREAT | os.O_EXCL will ensure that if a + # race condition happens between the os.remove and this line, that an + # error will be raised. Because we utilize a lockfile this should only + # happen if someone is attempting to attack us. + fd = os.open(filename, flags, fmode) + try: + return os.fdopen(fd, "wb") + + except: + # An error occurred wrapping our FD in a file object + os.close(fd) + raise + + +class FileCache(BaseCache): + + def __init__( + self, + directory, + forever=False, + filemode=0o0600, + dirmode=0o0700, + use_dir_lock=None, + lock_class=None, + ): + + if use_dir_lock is not None and lock_class is not None: + raise ValueError("Cannot use use_dir_lock and lock_class together") + + try: + from lockfile import LockFile + from lockfile.mkdirlockfile import MkdirLockFile + except ImportError: + notice = dedent( + """ + NOTE: In order to use the FileCache you must have + lockfile installed. You can install it via pip: + pip install lockfile + """ + ) + raise ImportError(notice) + + else: + if use_dir_lock: + lock_class = MkdirLockFile + + elif lock_class is None: + lock_class = LockFile + + self.directory = directory + self.forever = forever + self.filemode = filemode + self.dirmode = dirmode + self.lock_class = lock_class + + @staticmethod + def encode(x): + return hashlib.sha224(x.encode()).hexdigest() + + def _fn(self, name): + # NOTE: This method should not change as some may depend on it. + # See: https://github.com/ionrock/cachecontrol/issues/63 + hashed = self.encode(name) + parts = list(hashed[:5]) + [hashed] + return os.path.join(self.directory, *parts) + + def get(self, key): + name = self._fn(key) + try: + with open(name, "rb") as fh: + return fh.read() + + except FileNotFoundError: + return None + + def set(self, key, value): + name = self._fn(key) + + # Make sure the directory exists + try: + os.makedirs(os.path.dirname(name), self.dirmode) + except (IOError, OSError): + pass + + with self.lock_class(name) as lock: + # Write our actual file + with _secure_open_write(lock.path, self.filemode) as fh: + fh.write(value) + + def delete(self, key): + name = self._fn(key) + if not self.forever: + try: + os.remove(name) + except FileNotFoundError: + pass + + +def url_to_file_path(url, filecache): + """Return the file cache path based on the URL. + + This does not ensure the file exists! + """ + key = CacheController.cache_url(url) + return filecache._fn(key) diff --git a/robot/lib/python3.8/site-packages/cachecontrol/caches/redis_cache.py b/robot/lib/python3.8/site-packages/cachecontrol/caches/redis_cache.py new file mode 100644 index 0000000000000000000000000000000000000000..16da0aed96893309a3b5b05725e2ac155db7a67e --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/caches/redis_cache.py @@ -0,0 +1,33 @@ +from __future__ import division + +from datetime import datetime +from cachecontrol.cache import BaseCache + + +class RedisCache(BaseCache): + + def __init__(self, conn): + self.conn = conn + + def get(self, key): + return self.conn.get(key) + + def set(self, key, value, expires=None): + if not expires: + self.conn.set(key, value) + else: + expires = expires - datetime.utcnow() + self.conn.setex(key, int(expires.total_seconds()), value) + + def delete(self, key): + self.conn.delete(key) + + def clear(self): + """Helper for clearing all the keys in a database. Use with + caution!""" + for key in self.conn.keys(): + self.conn.delete(key) + + def close(self): + """Redis uses connection pooling, no need to close the connection.""" + pass diff --git a/robot/lib/python3.8/site-packages/cachecontrol/compat.py b/robot/lib/python3.8/site-packages/cachecontrol/compat.py new file mode 100644 index 0000000000000000000000000000000000000000..143c8ab08409f02d38b85a7fc3e8b4d0c7c5b448 --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/compat.py @@ -0,0 +1,29 @@ +try: + from urllib.parse import urljoin +except ImportError: + from urlparse import urljoin + + +try: + import cPickle as pickle +except ImportError: + import pickle + + +# Handle the case where the requests module has been patched to not have +# urllib3 bundled as part of its source. +try: + from requests.packages.urllib3.response import HTTPResponse +except ImportError: + from urllib3.response import HTTPResponse + +try: + from requests.packages.urllib3.util import is_fp_closed +except ImportError: + from urllib3.util import is_fp_closed + +# Replicate some six behaviour +try: + text_type = unicode +except NameError: + text_type = str diff --git a/robot/lib/python3.8/site-packages/cachecontrol/controller.py b/robot/lib/python3.8/site-packages/cachecontrol/controller.py new file mode 100644 index 0000000000000000000000000000000000000000..c5c4a50808baeded95191faa42c67722731aa06c --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/controller.py @@ -0,0 +1,376 @@ +""" +The httplib2 algorithms ported for use with requests. +""" +import logging +import re +import calendar +import time +from email.utils import parsedate_tz + +from requests.structures import CaseInsensitiveDict + +from .cache import DictCache +from .serialize import Serializer + + +logger = logging.getLogger(__name__) + +URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?") + + +def parse_uri(uri): + """Parses a URI using the regex given in Appendix B of RFC 3986. + + (scheme, authority, path, query, fragment) = parse_uri(uri) + """ + groups = URI.match(uri).groups() + return (groups[1], groups[3], groups[4], groups[6], groups[8]) + + +class CacheController(object): + """An interface to see if request should cached or not. + """ + + def __init__( + self, cache=None, cache_etags=True, serializer=None, status_codes=None + ): + self.cache = DictCache() if cache is None else cache + self.cache_etags = cache_etags + self.serializer = serializer or Serializer() + self.cacheable_status_codes = status_codes or (200, 203, 300, 301) + + @classmethod + def _urlnorm(cls, uri): + """Normalize the URL to create a safe key for the cache""" + (scheme, authority, path, query, fragment) = parse_uri(uri) + if not scheme or not authority: + raise Exception("Only absolute URIs are allowed. uri = %s" % uri) + + scheme = scheme.lower() + authority = authority.lower() + + if not path: + path = "/" + + # Could do syntax based normalization of the URI before + # computing the digest. See Section 6.2.2 of Std 66. + request_uri = query and "?".join([path, query]) or path + defrag_uri = scheme + "://" + authority + request_uri + + return defrag_uri + + @classmethod + def cache_url(cls, uri): + return cls._urlnorm(uri) + + def parse_cache_control(self, headers): + known_directives = { + # https://tools.ietf.org/html/rfc7234#section-5.2 + "max-age": (int, True), + "max-stale": (int, False), + "min-fresh": (int, True), + "no-cache": (None, False), + "no-store": (None, False), + "no-transform": (None, False), + "only-if-cached": (None, False), + "must-revalidate": (None, False), + "public": (None, False), + "private": (None, False), + "proxy-revalidate": (None, False), + "s-maxage": (int, True), + } + + cc_headers = headers.get("cache-control", headers.get("Cache-Control", "")) + + retval = {} + + for cc_directive in cc_headers.split(","): + if not cc_directive.strip(): + continue + + parts = cc_directive.split("=", 1) + directive = parts[0].strip() + + try: + typ, required = known_directives[directive] + except KeyError: + logger.debug("Ignoring unknown cache-control directive: %s", directive) + continue + + if not typ or not required: + retval[directive] = None + if typ: + try: + retval[directive] = typ(parts[1].strip()) + except IndexError: + if required: + logger.debug( + "Missing value for cache-control " "directive: %s", + directive, + ) + except ValueError: + logger.debug( + "Invalid value for cache-control directive " "%s, must be %s", + directive, + typ.__name__, + ) + + return retval + + def cached_request(self, request): + """ + Return a cached response if it exists in the cache, otherwise + return False. + """ + cache_url = self.cache_url(request.url) + logger.debug('Looking up "%s" in the cache', cache_url) + cc = self.parse_cache_control(request.headers) + + # Bail out if the request insists on fresh data + if "no-cache" in cc: + logger.debug('Request header has "no-cache", cache bypassed') + return False + + if "max-age" in cc and cc["max-age"] == 0: + logger.debug('Request header has "max_age" as 0, cache bypassed') + return False + + # Request allows serving from the cache, let's see if we find something + cache_data = self.cache.get(cache_url) + if cache_data is None: + logger.debug("No cache entry available") + return False + + # Check whether it can be deserialized + resp = self.serializer.loads(request, cache_data) + if not resp: + logger.warning("Cache entry deserialization failed, entry ignored") + return False + + # If we have a cached 301, return it immediately. We don't + # need to test our response for other headers b/c it is + # intrinsically "cacheable" as it is Permanent. + # See: + # https://tools.ietf.org/html/rfc7231#section-6.4.2 + # + # Client can try to refresh the value by repeating the request + # with cache busting headers as usual (ie no-cache). + if resp.status == 301: + msg = ( + 'Returning cached "301 Moved Permanently" response ' + "(ignoring date and etag information)" + ) + logger.debug(msg) + return resp + + headers = CaseInsensitiveDict(resp.headers) + if not headers or "date" not in headers: + if "etag" not in headers: + # Without date or etag, the cached response can never be used + # and should be deleted. + logger.debug("Purging cached response: no date or etag") + self.cache.delete(cache_url) + logger.debug("Ignoring cached response: no date") + return False + + now = time.time() + date = calendar.timegm(parsedate_tz(headers["date"])) + current_age = max(0, now - date) + logger.debug("Current age based on date: %i", current_age) + + # TODO: There is an assumption that the result will be a + # urllib3 response object. This may not be best since we + # could probably avoid instantiating or constructing the + # response until we know we need it. + resp_cc = self.parse_cache_control(headers) + + # determine freshness + freshness_lifetime = 0 + + # Check the max-age pragma in the cache control header + if "max-age" in resp_cc: + freshness_lifetime = resp_cc["max-age"] + logger.debug("Freshness lifetime from max-age: %i", freshness_lifetime) + + # If there isn't a max-age, check for an expires header + elif "expires" in headers: + expires = parsedate_tz(headers["expires"]) + if expires is not None: + expire_time = calendar.timegm(expires) - date + freshness_lifetime = max(0, expire_time) + logger.debug("Freshness lifetime from expires: %i", freshness_lifetime) + + # Determine if we are setting freshness limit in the + # request. Note, this overrides what was in the response. + if "max-age" in cc: + freshness_lifetime = cc["max-age"] + logger.debug( + "Freshness lifetime from request max-age: %i", freshness_lifetime + ) + + if "min-fresh" in cc: + min_fresh = cc["min-fresh"] + # adjust our current age by our min fresh + current_age += min_fresh + logger.debug("Adjusted current age from min-fresh: %i", current_age) + + # Return entry if it is fresh enough + if freshness_lifetime > current_age: + logger.debug('The response is "fresh", returning cached response') + logger.debug("%i > %i", freshness_lifetime, current_age) + return resp + + # we're not fresh. If we don't have an Etag, clear it out + if "etag" not in headers: + logger.debug('The cached response is "stale" with no etag, purging') + self.cache.delete(cache_url) + + # return the original handler + return False + + def conditional_headers(self, request): + cache_url = self.cache_url(request.url) + resp = self.serializer.loads(request, self.cache.get(cache_url)) + new_headers = {} + + if resp: + headers = CaseInsensitiveDict(resp.headers) + + if "etag" in headers: + new_headers["If-None-Match"] = headers["ETag"] + + if "last-modified" in headers: + new_headers["If-Modified-Since"] = headers["Last-Modified"] + + return new_headers + + def cache_response(self, request, response, body=None, status_codes=None): + """ + Algorithm for caching requests. + + This assumes a requests Response object. + """ + # From httplib2: Don't cache 206's since we aren't going to + # handle byte range requests + cacheable_status_codes = status_codes or self.cacheable_status_codes + if response.status not in cacheable_status_codes: + logger.debug( + "Status code %s not in %s", response.status, cacheable_status_codes + ) + return + + response_headers = CaseInsensitiveDict(response.headers) + + # If we've been given a body, our response has a Content-Length, that + # Content-Length is valid then we can check to see if the body we've + # been given matches the expected size, and if it doesn't we'll just + # skip trying to cache it. + if ( + body is not None + and "content-length" in response_headers + and response_headers["content-length"].isdigit() + and int(response_headers["content-length"]) != len(body) + ): + return + + cc_req = self.parse_cache_control(request.headers) + cc = self.parse_cache_control(response_headers) + + cache_url = self.cache_url(request.url) + logger.debug('Updating cache with response from "%s"', cache_url) + + # Delete it from the cache if we happen to have it stored there + no_store = False + if "no-store" in cc: + no_store = True + logger.debug('Response header has "no-store"') + if "no-store" in cc_req: + no_store = True + logger.debug('Request header has "no-store"') + if no_store and self.cache.get(cache_url): + logger.debug('Purging existing cache entry to honor "no-store"') + self.cache.delete(cache_url) + if no_store: + return + + # https://tools.ietf.org/html/rfc7234#section-4.1: + # A Vary header field-value of "*" always fails to match. + # Storing such a response leads to a deserialization warning + # during cache lookup and is not allowed to ever be served, + # so storing it can be avoided. + if "*" in response_headers.get("vary", ""): + logger.debug('Response header has "Vary: *"') + return + + # If we've been given an etag, then keep the response + if self.cache_etags and "etag" in response_headers: + logger.debug("Caching due to etag") + self.cache.set( + cache_url, self.serializer.dumps(request, response, body=body) + ) + + # Add to the cache any 301s. We do this before looking that + # the Date headers. + elif response.status == 301: + logger.debug("Caching permanant redirect") + self.cache.set(cache_url, self.serializer.dumps(request, response)) + + # Add to the cache if the response headers demand it. If there + # is no date header then we can't do anything about expiring + # the cache. + elif "date" in response_headers: + # cache when there is a max-age > 0 + if "max-age" in cc and cc["max-age"] > 0: + logger.debug("Caching b/c date exists and max-age > 0") + self.cache.set( + cache_url, self.serializer.dumps(request, response, body=body) + ) + + # If the request can expire, it means we should cache it + # in the meantime. + elif "expires" in response_headers: + if response_headers["expires"]: + logger.debug("Caching b/c of expires header") + self.cache.set( + cache_url, self.serializer.dumps(request, response, body=body) + ) + + def update_cached_response(self, request, response): + """On a 304 we will get a new set of headers that we want to + update our cached value with, assuming we have one. + + This should only ever be called when we've sent an ETag and + gotten a 304 as the response. + """ + cache_url = self.cache_url(request.url) + + cached_response = self.serializer.loads(request, self.cache.get(cache_url)) + + if not cached_response: + # we didn't have a cached response + return response + + # Lets update our headers with the headers from the new request: + # http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-26#section-4.1 + # + # The server isn't supposed to send headers that would make + # the cached body invalid. But... just in case, we'll be sure + # to strip out ones we know that might be problmatic due to + # typical assumptions. + excluded_headers = ["content-length"] + + cached_response.headers.update( + dict( + (k, v) + for k, v in response.headers.items() + if k.lower() not in excluded_headers + ) + ) + + # we want a 200 b/c we have content via the cache + cached_response.status = 200 + + # update our cache + self.cache.set(cache_url, self.serializer.dumps(request, cached_response)) + + return cached_response diff --git a/robot/lib/python3.8/site-packages/cachecontrol/filewrapper.py b/robot/lib/python3.8/site-packages/cachecontrol/filewrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..30ed4c5a62a99906ab662a8acfba2ab75e82af2e --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/filewrapper.py @@ -0,0 +1,80 @@ +from io import BytesIO + + +class CallbackFileWrapper(object): + """ + Small wrapper around a fp object which will tee everything read into a + buffer, and when that file is closed it will execute a callback with the + contents of that buffer. + + All attributes are proxied to the underlying file object. + + This class uses members with a double underscore (__) leading prefix so as + not to accidentally shadow an attribute. + """ + + def __init__(self, fp, callback): + self.__buf = BytesIO() + self.__fp = fp + self.__callback = callback + + def __getattr__(self, name): + # The vaguaries of garbage collection means that self.__fp is + # not always set. By using __getattribute__ and the private + # name[0] allows looking up the attribute value and raising an + # AttributeError when it doesn't exist. This stop thigns from + # infinitely recursing calls to getattr in the case where + # self.__fp hasn't been set. + # + # [0] https://docs.python.org/2/reference/expressions.html#atom-identifiers + fp = self.__getattribute__("_CallbackFileWrapper__fp") + return getattr(fp, name) + + def __is_fp_closed(self): + try: + return self.__fp.fp is None + + except AttributeError: + pass + + try: + return self.__fp.closed + + except AttributeError: + pass + + # We just don't cache it then. + # TODO: Add some logging here... + return False + + def _close(self): + if self.__callback: + self.__callback(self.__buf.getvalue()) + + # We assign this to None here, because otherwise we can get into + # really tricky problems where the CPython interpreter dead locks + # because the callback is holding a reference to something which + # has a __del__ method. Setting this to None breaks the cycle + # and allows the garbage collector to do it's thing normally. + self.__callback = None + + def read(self, amt=None): + data = self.__fp.read(amt) + self.__buf.write(data) + if self.__is_fp_closed(): + self._close() + + return data + + def _safe_read(self, amt): + data = self.__fp._safe_read(amt) + if amt == 2 and data == b"\r\n": + # urllib executes this read to toss the CRLF at the end + # of the chunk. + return data + + self.__buf.write(data) + if self.__is_fp_closed(): + self._close() + + return data diff --git a/robot/lib/python3.8/site-packages/cachecontrol/heuristics.py b/robot/lib/python3.8/site-packages/cachecontrol/heuristics.py new file mode 100644 index 0000000000000000000000000000000000000000..6c0e9790d5d0764f2ca005ae955d644bc2098d75 --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/heuristics.py @@ -0,0 +1,135 @@ +import calendar +import time + +from email.utils import formatdate, parsedate, parsedate_tz + +from datetime import datetime, timedelta + +TIME_FMT = "%a, %d %b %Y %H:%M:%S GMT" + + +def expire_after(delta, date=None): + date = date or datetime.utcnow() + return date + delta + + +def datetime_to_header(dt): + return formatdate(calendar.timegm(dt.timetuple())) + + +class BaseHeuristic(object): + + def warning(self, response): + """ + Return a valid 1xx warning header value describing the cache + adjustments. + + The response is provided too allow warnings like 113 + http://tools.ietf.org/html/rfc7234#section-5.5.4 where we need + to explicitly say response is over 24 hours old. + """ + return '110 - "Response is Stale"' + + def update_headers(self, response): + """Update the response headers with any new headers. + + NOTE: This SHOULD always include some Warning header to + signify that the response was cached by the client, not + by way of the provided headers. + """ + return {} + + def apply(self, response): + updated_headers = self.update_headers(response) + + if updated_headers: + response.headers.update(updated_headers) + warning_header_value = self.warning(response) + if warning_header_value is not None: + response.headers.update({"Warning": warning_header_value}) + + return response + + +class OneDayCache(BaseHeuristic): + """ + Cache the response by providing an expires 1 day in the + future. + """ + + def update_headers(self, response): + headers = {} + + if "expires" not in response.headers: + date = parsedate(response.headers["date"]) + expires = expire_after(timedelta(days=1), date=datetime(*date[:6])) + headers["expires"] = datetime_to_header(expires) + headers["cache-control"] = "public" + return headers + + +class ExpiresAfter(BaseHeuristic): + """ + Cache **all** requests for a defined time period. + """ + + def __init__(self, **kw): + self.delta = timedelta(**kw) + + def update_headers(self, response): + expires = expire_after(self.delta) + return {"expires": datetime_to_header(expires), "cache-control": "public"} + + def warning(self, response): + tmpl = "110 - Automatically cached for %s. Response might be stale" + return tmpl % self.delta + + +class LastModified(BaseHeuristic): + """ + If there is no Expires header already, fall back on Last-Modified + using the heuristic from + http://tools.ietf.org/html/rfc7234#section-4.2.2 + to calculate a reasonable value. + + Firefox also does something like this per + https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ + http://lxr.mozilla.org/mozilla-release/source/netwerk/protocol/http/nsHttpResponseHead.cpp#397 + Unlike mozilla we limit this to 24-hr. + """ + cacheable_by_default_statuses = { + 200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501 + } + + def update_headers(self, resp): + headers = resp.headers + + if "expires" in headers: + return {} + + if "cache-control" in headers and headers["cache-control"] != "public": + return {} + + if resp.status not in self.cacheable_by_default_statuses: + return {} + + if "date" not in headers or "last-modified" not in headers: + return {} + + date = calendar.timegm(parsedate_tz(headers["date"])) + last_modified = parsedate(headers["last-modified"]) + if date is None or last_modified is None: + return {} + + now = time.time() + current_age = max(0, now - date) + delta = date - calendar.timegm(last_modified) + freshness_lifetime = max(0, min(delta / 10, 24 * 3600)) + if freshness_lifetime <= current_age: + return {} + + expires = date + freshness_lifetime + return {"expires": time.strftime(TIME_FMT, time.gmtime(expires))} + + def warning(self, resp): + return None diff --git a/robot/lib/python3.8/site-packages/cachecontrol/serialize.py b/robot/lib/python3.8/site-packages/cachecontrol/serialize.py new file mode 100644 index 0000000000000000000000000000000000000000..572cf0e6c871c83bbc03124505c53e4af9ee60c5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/serialize.py @@ -0,0 +1,188 @@ +import base64 +import io +import json +import zlib + +import msgpack +from requests.structures import CaseInsensitiveDict + +from .compat import HTTPResponse, pickle, text_type + + +def _b64_decode_bytes(b): + return base64.b64decode(b.encode("ascii")) + + +def _b64_decode_str(s): + return _b64_decode_bytes(s).decode("utf8") + + +class Serializer(object): + + def dumps(self, request, response, body=None): + response_headers = CaseInsensitiveDict(response.headers) + + if body is None: + body = response.read(decode_content=False) + + # NOTE: 99% sure this is dead code. I'm only leaving it + # here b/c I don't have a test yet to prove + # it. Basically, before using + # `cachecontrol.filewrapper.CallbackFileWrapper`, + # this made an effort to reset the file handle. The + # `CallbackFileWrapper` short circuits this code by + # setting the body as the content is consumed, the + # result being a `body` argument is *always* passed + # into cache_response, and in turn, + # `Serializer.dump`. + response._fp = io.BytesIO(body) + + # NOTE: This is all a bit weird, but it's really important that on + # Python 2.x these objects are unicode and not str, even when + # they contain only ascii. The problem here is that msgpack + # understands the difference between unicode and bytes and we + # have it set to differentiate between them, however Python 2 + # doesn't know the difference. Forcing these to unicode will be + # enough to have msgpack know the difference. + data = { + u"response": { + u"body": body, + u"headers": dict( + (text_type(k), text_type(v)) for k, v in response.headers.items() + ), + u"status": response.status, + u"version": response.version, + u"reason": text_type(response.reason), + u"strict": response.strict, + u"decode_content": response.decode_content, + } + } + + # Construct our vary headers + data[u"vary"] = {} + if u"vary" in response_headers: + varied_headers = response_headers[u"vary"].split(",") + for header in varied_headers: + header = text_type(header).strip() + header_value = request.headers.get(header, None) + if header_value is not None: + header_value = text_type(header_value) + data[u"vary"][header] = header_value + + return b",".join([b"cc=4", msgpack.dumps(data, use_bin_type=True)]) + + def loads(self, request, data): + # Short circuit if we've been given an empty set of data + if not data: + return + + # Determine what version of the serializer the data was serialized + # with + try: + ver, data = data.split(b",", 1) + except ValueError: + ver = b"cc=0" + + # Make sure that our "ver" is actually a version and isn't a false + # positive from a , being in the data stream. + if ver[:3] != b"cc=": + data = ver + data + ver = b"cc=0" + + # Get the version number out of the cc=N + ver = ver.split(b"=", 1)[-1].decode("ascii") + + # Dispatch to the actual load method for the given version + try: + return getattr(self, "_loads_v{}".format(ver))(request, data) + + except AttributeError: + # This is a version we don't have a loads function for, so we'll + # just treat it as a miss and return None + return + + def prepare_response(self, request, cached): + """Verify our vary headers match and construct a real urllib3 + HTTPResponse object. + """ + # Special case the '*' Vary value as it means we cannot actually + # determine if the cached response is suitable for this request. + # This case is also handled in the controller code when creating + # a cache entry, but is left here for backwards compatibility. + if "*" in cached.get("vary", {}): + return + + # Ensure that the Vary headers for the cached response match our + # request + for header, value in cached.get("vary", {}).items(): + if request.headers.get(header, None) != value: + return + + body_raw = cached["response"].pop("body") + + headers = CaseInsensitiveDict(data=cached["response"]["headers"]) + if headers.get("transfer-encoding", "") == "chunked": + headers.pop("transfer-encoding") + + cached["response"]["headers"] = headers + + try: + body = io.BytesIO(body_raw) + except TypeError: + # This can happen if cachecontrol serialized to v1 format (pickle) + # using Python 2. A Python 2 str(byte string) will be unpickled as + # a Python 3 str (unicode string), which will cause the above to + # fail with: + # + # TypeError: 'str' does not support the buffer interface + body = io.BytesIO(body_raw.encode("utf8")) + + return HTTPResponse(body=body, preload_content=False, **cached["response"]) + + def _loads_v0(self, request, data): + # The original legacy cache data. This doesn't contain enough + # information to construct everything we need, so we'll treat this as + # a miss. + return + + def _loads_v1(self, request, data): + try: + cached = pickle.loads(data) + except ValueError: + return + + return self.prepare_response(request, cached) + + def _loads_v2(self, request, data): + try: + cached = json.loads(zlib.decompress(data).decode("utf8")) + except (ValueError, zlib.error): + return + + # We need to decode the items that we've base64 encoded + cached["response"]["body"] = _b64_decode_bytes(cached["response"]["body"]) + cached["response"]["headers"] = dict( + (_b64_decode_str(k), _b64_decode_str(v)) + for k, v in cached["response"]["headers"].items() + ) + cached["response"]["reason"] = _b64_decode_str(cached["response"]["reason"]) + cached["vary"] = dict( + (_b64_decode_str(k), _b64_decode_str(v) if v is not None else v) + for k, v in cached["vary"].items() + ) + + return self.prepare_response(request, cached) + + def _loads_v3(self, request, data): + # Due to Python 2 encoding issues, it's impossible to know for sure + # exactly how to load v3 entries, thus we'll treat these as a miss so + # that they get rewritten out as v4 entries. + return + + def _loads_v4(self, request, data): + try: + cached = msgpack.loads(data, raw=False) + except ValueError: + return + + return self.prepare_response(request, cached) diff --git a/robot/lib/python3.8/site-packages/cachecontrol/wrapper.py b/robot/lib/python3.8/site-packages/cachecontrol/wrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..d8e6fc6a9e3bbe8b9b3c032298deb08e764d32f8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/cachecontrol/wrapper.py @@ -0,0 +1,29 @@ +from .adapter import CacheControlAdapter +from .cache import DictCache + + +def CacheControl( + sess, + cache=None, + cache_etags=True, + serializer=None, + heuristic=None, + controller_class=None, + adapter_class=None, + cacheable_methods=None, +): + + cache = DictCache() if cache is None else cache + adapter_class = adapter_class or CacheControlAdapter + adapter = adapter_class( + cache, + cache_etags=cache_etags, + serializer=serializer, + heuristic=heuristic, + controller_class=controller_class, + cacheable_methods=cacheable_methods, + ) + sess.mount("http://", adapter) + sess.mount("https://", adapter) + + return sess diff --git a/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/AUTHORS.txt b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/AUTHORS.txt new file mode 100644 index 0000000000000000000000000000000000000000..72c87d7d38ae7bf859717c333a5ee8230f6ce624 --- /dev/null +++ b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/AUTHORS.txt @@ -0,0 +1,562 @@ +A_Rog +Aakanksha Agrawal <11389424+rasponic@users.noreply.github.com> +Abhinav Sagar <40603139+abhinavsagar@users.noreply.github.com> +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Tse +Adam Wentz +admin +Adrien Morison +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Aleks Bunin +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Alexey Popravka +Alli +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrey Bulgakov +Andrés Delfino <34587441+andresdelfino@users.noreply.github.com> +Andrés Delfino +Andy Freeland +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anuj Godase +AQNOUCH Mohammed +AraHaan +Arindam Choudhury +Armin Ronacher +Artem +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avner Cohen +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Darnell +Ben Hoyt +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Brandon L. Reiss +Brandt Bucher +Brett Randall +Brian Cristante <33549821+brcrista@users.noreply.github.com> +Brian Cristante +Brian Rosner +BrownTruck +Bruno Oliveira +Bruno Renié +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +c22 +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Chih-Hsuan Yen +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris McDonough +Chris Wolfe +Christian Heimes +Christian Oudard +Christopher Hunt +Christopher Snyder +Clark Boylan +Clay McClure +Cody +Cody Soyland +Colin Watson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Curtis Doty +cytolentino +Damian Quiroga +Dan Black +Dan Savilonis +Dan Sully +daniel +Daniel Collins +Daniel Hahler +Daniel Holth +Daniel Jost +Daniel Shaulov +Daniele Esposti +Daniele Procida +Danny Hermes +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Bordeynik +David Caro +David Evans +David Linke +David Pursehouse +David Tucker +David Wales +Davidovich +derwolfe +Desetude +Diego Caraballo +DiegoCaraballo +Dmitry Gladkov +Domen Kožar +Donald Stufft +Dongweiming +Douglas Thor +DrFeathers +Dustin Ingram +Dwayne Bailey +Ed Morley <501702+edmorley@users.noreply.github.com> +Ed Morley +Eitan Adler +ekristina +elainechan +Eli Schwartz +Eli Schwartz +Emil Burzo +Emil Styrke +Endoh Takanao +enoch +Erdinc Mutlu +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Ernest W Durbin III +Ernest W. Durbin III +Erwin Janssen +Eugene Vereshchagin +everdimension +Felix Yan +fiber-space +Filip Kokosiński +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gdanielson +Geoffrey Lehée +Geoffrey Sneddon +George Song +Georgi Valkov +Giftlin Rajaiah +gizmoguy1 +gkdoc <40815324+gkdoc@users.noreply.github.com> +Gopinath M <31352222+mgopi1990@users.noreply.github.com> +GOTO Hayato <3532528+gh640@users.noreply.github.com> +gpiks +Guilherme Espada +Guy Rozendorn +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +Herbert Pfennig +Hsiaoming Yang +Hugo +Hugo Lopes Tavares +Hugo van Kemenade +hugovk +Hynek Schlawack +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ilya Baryshev +INADA Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Ivan Pozdeev +Jacob Kim +jakirkham +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James Cleveland +James Cleveland +James Firth +James Polley +Jan Pokorný +Jannis Leidel +jarondl +Jason R. Coombs +Jay Graves +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jelmer Vernooij +jenix21 +Jeremy Stanley +Jeremy Zafran +Jiashuo Li +Jim Garrison +Jivan Amara +John Paton +John-Scott Atlakson +johnthagen +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joost Molenaar +Jorge Niedbalski +Joseph Long +Josh Bronson +Josh Hansen +Josh Schneier +Juanjo Bazán +Julian Berman +Julian Gethmann +Julien Demoor +jwg4 +Jyrki Pulliainen +Kai Chen +Kamal Bin Mustafa +kaustav haldar +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kexuan Sun +Kit Randel +kpinc +Krishna Oza +Kumar McMillan +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurie Opperman +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +Loren Carvalho +Lucas Cimon +Ludovic Gasc +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +László Kiss Kollár +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark Williams +Mark Williams +Markus Hametner +Masaki +Masklinn +Matej Stuchlik +Mathew Jennings +Mathieu Bridon +Matt Good +Matt Maker +Matt Robenolt +matthew +Matthew Einhorn +Matthew Gilliard +Matthew Iversen +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna <44498973+mbaluna@users.noreply.github.com> +mdebi <17590103+mdebi@users.noreply.github.com> +memoselyk +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Williamson +michaelpacer +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Monica Baluna +montefra +Monty Taylor +Nate Coraor +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nikhil Benesch +Nitesh Sharma +Nowell Strite +NtaleGrey +nvdv +Ofekmeister +ofrinevo +Oliver Jeeves +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +OMOTO Kenji +Omry Yadan +Oren Held +Oscar Benjamin +Oz N Tiram +Pachwenko <32424503+Pachwenko@users.noreply.github.com> +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavithra Eswaramoorthy <33131404+QueenCoffee@users.noreply.github.com> +Pawel Jasinski +Pekka Klärck +Peter Lisák +Peter Waller +petr-tik +Phaneendra Chiruvella +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Pratik Mallya +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal <7895pulkit@gmail.com> +Qiangning Hong +Quentin Pradet +R. David Murray +Rafael Caricio +Ralf Schmitt +Razzi Abuissa +rdb +Remi Rampin +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Richard Jones +RobberPhex +Robert Collins +Robert McGibbon +Robert T. McGibbon +robin elisha robinson +Roey Berman +Rohan Jain +Rohan Jain +Rohan Jain +Roman Bogorodskiy +Romuald Brunet +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +Roy Wellington Ⅳ +Ryan Wooden +ryneeverett +Sachi King +Salvatore Rinchiera +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sergey Vasilyev +Seth Woodworth +Shlomi Fish +Shovan Maity +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +Sorin Sbarnea +Stavros Korokithakis +Stefan Scherfke +Stephan Erb +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +stonebig +Stéphane Bidoul (ACSONE) +Stéphane Bidoul +Stéphane Klein +Sumana Harihareswara +Sviatoslav Sydorenko +Sviatoslav Sydorenko +Swat009 +Takayuki SHIMIZUKAWA +tbeswick +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tom Forbes +Tom Freudenheim +Tom V +Tomas Orsava +Tomer Chachamu +Tony Beswick +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +Travis Swicegood +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Philippon +Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> +Vitaly Babiy +Vladimir Rutsky +W. Trevor King +Wil Tan +Wilfred Hughes +William ML Leslie +William T Olson +Wilson Mo +wim glenn +Wolfgang Maier +Xavier Fernandez +Xavier Fernandez +xoviat +xtreak +YAMAMOTO Takashi +Yen Chi Hsuan +Yeray Diaz Diaz +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Zearin +Zearin +Zhiping Deng +Zvezdan Petkovic +Łukasz Langa +Семён Марьясин diff --git a/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..737fec5c5352af3d9a6a47a0670da4bdb52c5725 --- /dev/null +++ b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2019 The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/METADATA b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..e897b3d8335b63a805b5bc59da1446fca1a95730 --- /dev/null +++ b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/METADATA @@ -0,0 +1,74 @@ +Metadata-Version: 2.1 +Name: certifi +Version: 2019.11.28 +Summary: Python package for providing Mozilla's CA Bundle. +Home-page: https://certifi.io/ +Author: Kenneth Reitz +Author-email: me@kennethreitz.com +License: MPL-2.0 +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 + +Certifi: Python SSL Certificates +================================ + +`Certifi`_ is a carefully curated collection of Root Certificates for +validating the trustworthiness of SSL certificates while verifying the identity +of TLS hosts. It has been extracted from the `Requests`_ project. + +Installation +------------ + +``certifi`` is available on PyPI. Simply install it with ``pip``:: + + $ pip install certifi + +Usage +----- + +To reference the installed certificate authority (CA) bundle, you can use the +built-in function:: + + >>> import certifi + + >>> certifi.where() + '/usr/local/lib/python2.7/site-packages/certifi/cacert.pem' + +Or from the command line:: + + $ python -m certifi + /usr/local/lib/python2.7/site-packages/certifi/cacert.pem + +Enjoy! + +1024-bit Root Certificates +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Browsers and certificate authorities have concluded that 1024-bit keys are +unacceptably weak for certificates, particularly root certificates. For this +reason, Mozilla has removed any weak (i.e. 1024-bit key) certificate from its +bundle, replacing it with an equivalent strong (i.e. 2048-bit or greater key) +certificate from the same CA. Because Mozilla removed these certificates from +its bundle, ``certifi`` removed them as well. + +In previous versions, ``certifi`` provided the ``certifi.old_where()`` function +to intentionally re-add the 1024-bit roots back into your bundle. This was not +recommended in production and therefore was removed at the end of 2018. + +.. _`Certifi`: https://certifi.io/en/latest/ +.. _`Requests`: http://docs.python-requests.org/en/latest/ + + diff --git a/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/RECORD b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..1a995d45f16219825d4e4341cc07ab94c9d24bc5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/RECORD @@ -0,0 +1,17 @@ +certifi/__init__.py,sha256=JVwzDhkMttyVVtfNDrU_i0v2a-WmtEBXq0Z8oz4Ghzk,52 +certifi/__main__.py,sha256=FiOYt1Fltst7wk9DRa6GCoBr8qBUxlNQu_MKJf04E6s,41 +certifi/cacert.pem,sha256=cyvv5Jx1gHACNEj2GaOrsIj0Tk8FmSvHR42uhzvlatg,281457 +certifi/core.py,sha256=u_450edAVoiZrgqi6k3Sekcvs-B1zD_hTeE6UJ2OcQ4,225 +certifi-2019.11.28.dist-info/AUTHORS.txt,sha256=RtqU9KfonVGhI48DAA4-yTOBUhBtQTjFhaDzHoyh7uU,21518 +certifi-2019.11.28.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090 +certifi-2019.11.28.dist-info/METADATA,sha256=K6ioZZT9N0bRETHmncjK-UJKAFIVF26h1F31dfDaV5k,2523 +certifi-2019.11.28.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +certifi-2019.11.28.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8 +certifi-2019.11.28.dist-info/RECORD,, +certifi/__pycache__,, +certifi/__main__.cpython-38.pyc,, +certifi-2019.11.28.virtualenv,, +certifi/core.cpython-38.pyc,, +certifi-2019.11.28.dist-info/INSTALLER,, +certifi-2019.11.28.dist-info/__pycache__,, +certifi/__init__.cpython-38.pyc,, \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/WHEEL b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ef99c6cf3283b50a273ac4c6d009a0aa85597070 --- /dev/null +++ b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..963eac530b9bc28d704d1bc410299c68e3216d4d --- /dev/null +++ b/robot/lib/python3.8/site-packages/certifi-2019.11.28.dist-info/top_level.txt @@ -0,0 +1 @@ +certifi diff --git a/robot/lib/python3.8/site-packages/certifi-2019.11.28.virtualenv b/robot/lib/python3.8/site-packages/certifi-2019.11.28.virtualenv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/certifi/__init__.py b/robot/lib/python3.8/site-packages/certifi/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0d59a05630e7ff963947b2596194d12248a41863 --- /dev/null +++ b/robot/lib/python3.8/site-packages/certifi/__init__.py @@ -0,0 +1,3 @@ +from .core import where + +__version__ = "2019.11.28" diff --git a/robot/lib/python3.8/site-packages/certifi/__main__.py b/robot/lib/python3.8/site-packages/certifi/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..5f1da0dd0c201e8aedc1552ef82a1780eb37276d --- /dev/null +++ b/robot/lib/python3.8/site-packages/certifi/__main__.py @@ -0,0 +1,2 @@ +from certifi import where +print(where()) diff --git a/robot/lib/python3.8/site-packages/certifi/cacert.pem b/robot/lib/python3.8/site-packages/certifi/cacert.pem new file mode 100644 index 0000000000000000000000000000000000000000..a4758ef3afb0b0299485e759d9a8c5e4747fc96d --- /dev/null +++ b/robot/lib/python3.8/site-packages/certifi/cacert.pem @@ -0,0 +1,4602 @@ + +# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Label: "GlobalSign Root CA" +# Serial: 4835703278459707669005204 +# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a +# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c +# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Label: "GlobalSign Root CA - R2" +# Serial: 4835703278459682885658125 +# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 +# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe +# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 +MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL +v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 +eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq +tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd +C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa +zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB +mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH +V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n +bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG +3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs +J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO +291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS +ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd +AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Label: "Verisign Class 3 Public Primary Certification Authority - G3" +# Serial: 206684696279472310254277870180966723415 +# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 +# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 +# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b +N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t +KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu +kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm +CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ +Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu +imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te +2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe +DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p +F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt +TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Label: "Entrust.net Premium 2048 Secure Server CA" +# Serial: 946069240 +# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 +# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 +# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 +MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub +j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo +U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b +u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ +bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er +fF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Label: "Baltimore CyberTrust Root" +# Serial: 33554617 +# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 +# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 +# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Label: "AddTrust External Root" +# Serial: 1 +# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f +# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 +# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs +IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 +MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h +bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt +H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 +uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX +mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX +a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN +E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 +WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD +VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 +Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU +cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx +IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN +AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH +YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC +Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX +c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a +mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Label: "Entrust Root Certification Authority" +# Serial: 1164660820 +# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 +# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 +# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Global CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. +# Label: "GeoTrust Global CA" +# Serial: 144470 +# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 +# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 +# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg +R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 +9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq +fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv +iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU +1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ +bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW +MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA +ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l +uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn +Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS +tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF +PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un +hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV +5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Label: "GeoTrust Universal CA" +# Serial: 1 +# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 +# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 +# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy +c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 +IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV +VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 +cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT +QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh +F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v +c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w +mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd +VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX +teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ +f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe +Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ +nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY +MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX +IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn +ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z +uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN +Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja +QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW +koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 +ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt +DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm +bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Label: "GeoTrust Universal CA 2" +# Serial: 1 +# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 +# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 +# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy +c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD +VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 +c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 +WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG +FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq +XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL +se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb +KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd +IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 +y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt +hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc +QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 +Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV +HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ +KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ +L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr +Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo +ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY +T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz +GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m +1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV +OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH +6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX +QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +# Issuer: CN=AAA Certificate Services O=Comodo CA Limited +# Subject: CN=AAA Certificate Services O=Comodo CA Limited +# Label: "Comodo AAA Services root" +# Serial: 1 +# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 +# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 +# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Label: "QuoVadis Root CA" +# Serial: 985026699 +# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 +# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 +# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz +MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw +IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR +dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp +li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D +rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ +WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug +F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU +xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC +Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv +dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw +ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl +IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh +c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy +ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI +KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T +KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq +y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p +dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD +VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk +fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 +7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R +cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y +mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW +xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK +SnQ2+Q== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2" +# Serial: 1289 +# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b +# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 +# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3" +# Serial: 1478 +# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf +# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 +# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB +4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr +H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd +8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv +vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT +mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe +btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc +T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt +WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ +c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A +4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD +VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG +CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 +aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw +czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G +A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg +Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 +7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem +d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd ++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B +4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN +t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x +DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 +k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s +zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j +Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT +mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK +4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 +# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 +# Label: "Security Communication Root CA" +# Serial: 0 +# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a +# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 +# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY +MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t +dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 +WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD +VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 +9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ +DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 +Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N +QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ +xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G +A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG +kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr +Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 +Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU +JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot +RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== +-----END CERTIFICATE----- + +# Issuer: CN=Sonera Class2 CA O=Sonera +# Subject: CN=Sonera Class2 CA O=Sonera +# Label: "Sonera Class 2 Root CA" +# Serial: 29 +# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb +# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 +# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP +MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx +MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV +BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o +Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt +5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s +3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej +vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu +8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw +DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG +MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil +zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ +3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD +FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 +Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 +ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M +-----END CERTIFICATE----- + +# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Label: "XRamp Global CA Root" +# Serial: 107108908803651509692980124233745014957 +# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 +# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 +# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Label: "Go Daddy Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 +# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 +# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- + +# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Label: "Starfield Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 +# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a +# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +# Issuer: O=Government Root Certification Authority +# Subject: O=Government Root Certification Authority +# Label: "Taiwan GRCA" +# Serial: 42023070807708724159991140556527066870 +# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e +# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 +# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ +MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow +PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR +IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q +gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy +yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts +F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 +jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx +ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC +VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK +YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH +EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN +Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud +DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE +MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK +UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf +qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK +ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE +JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 +hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 +EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm +nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX +udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz +ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe +LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl +pYYsfPQS +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root CA" +# Serial: 17154717934120587862167794914071425081 +# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 +# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 +# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root CA" +# Serial: 10944719598952040374951832963794454346 +# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e +# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 +# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert High Assurance EV Root CA" +# Serial: 3553400076410547919724730734378100087 +# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a +# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 +# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- + +# Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. +# Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. +# Label: "DST Root CA X3" +# Serial: 91299735575339953335919266965803778155 +# MD5 Fingerprint: 41:03:52:dc:0f:f7:50:1b:16:f0:02:8e:ba:6f:45:c5 +# SHA1 Fingerprint: da:c9:02:4f:54:d8:f6:df:94:93:5f:b1:73:26:38:ca:6a:d7:7c:13 +# SHA256 Fingerprint: 06:87:26:03:31:a7:24:03:d9:09:f1:05:e6:9b:cf:0d:32:e1:bd:24:93:ff:c6:d9:20:6d:11:bc:d6:77:07:39 +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Label: "SwissSign Gold CA - G2" +# Serial: 13492815561806991280 +# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 +# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 +# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln +biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF +MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT +d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 +76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ +bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c +6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE +emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd +MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt +MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y +MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y +FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi +aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM +gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB +qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 +lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn +8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 +45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO +UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 +O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC +bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv +GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a +77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC +hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 +92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp +Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w +ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt +Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Label: "SwissSign Silver CA - G2" +# Serial: 5700383053117599563 +# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 +# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb +# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE +BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu +IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow +RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY +U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv +Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br +YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF +nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH +6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt +eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/ +c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ +MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH +HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf +jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6 +5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB +rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c +wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB +AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp +WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9 +xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ +2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ +IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8 +aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X +em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR +dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/ +OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+ +hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy +tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Label: "GeoTrust Primary Certification Authority" +# Serial: 32798226551256963324313806436981982369 +# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf +# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 +# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY +MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo +R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx +MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 +AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA +ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 +7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W +kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI +mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ +KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 +6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl +4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K +oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj +UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU +AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA" +# Serial: 69529181992039203566298953787712940909 +# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 +# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 +# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB +qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV +BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw +NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j +LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG +A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs +W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta +3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk +6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 +Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J +NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP +r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU +DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz +YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 +/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ +LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 +jVaMaA== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" +# Serial: 33037644167568058970164719475676101450 +# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c +# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 +# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 +nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex +t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz +SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG +BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ +rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ +NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH +BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv +MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE +p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y +5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK +WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ +4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N +hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- + +# Issuer: CN=SecureTrust CA O=SecureTrust Corporation +# Subject: CN=SecureTrust CA O=SecureTrust Corporation +# Label: "SecureTrust CA" +# Serial: 17199774589125277788362757014266862032 +# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 +# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 +# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +# Issuer: CN=Secure Global CA O=SecureTrust Corporation +# Subject: CN=Secure Global CA O=SecureTrust Corporation +# Label: "Secure Global CA" +# Serial: 9751836167731051554232119481456978597 +# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de +# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b +# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx +MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg +Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ +iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa +/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ +jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI +HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 +sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w +gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw +KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG +AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L +URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO +H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm +I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY +iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO Certification Authority O=COMODO CA Limited +# Label: "COMODO Certification Authority" +# Serial: 104350513648249232941998508985834464573 +# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 +# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b +# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW +/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g +PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u +QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY +SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv +IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 +zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd +BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB +ZQ== +-----END CERTIFICATE----- + +# Issuer: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Label: "Network Solutions Certificate Authority" +# Serial: 116697915152937497490437556386812487904 +# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e +# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce +# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi +MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV +UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO +ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz +c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP +OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl +mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF +BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 +qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw +gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu +bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp +dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 +6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ +h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH +/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN +pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Label: "COMODO ECC Certification Authority" +# Serial: 41578283867086692638256921589707938090 +# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 +# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 +# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GA CA" +# Serial: 86718877871133159090080555911823548314 +# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 +# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 +# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB +ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly +aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w +NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G +A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX +SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR +VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 +w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF +mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg +4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 +4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw +EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx +SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 +ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 +vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi +Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ +/L7fCg0= +-----END CERTIFICATE----- + +# Issuer: CN=Certigna O=Dhimyotis +# Subject: CN=Certigna O=Dhimyotis +# Label: "Certigna" +# Serial: 18364802974209362175 +# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff +# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 +# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc +# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc +# Label: "Cybertrust Global Root" +# Serial: 4835703278459682877484360 +# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 +# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 +# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG +A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh +bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE +ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS +b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5 +7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS +J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y +HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP +t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz +FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY +XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw +hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js +MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA +A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj +Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx +XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o +omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc +A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- + +# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Label: "ePKI Root Certification Authority" +# Serial: 28956088682735189655030529057352760477 +# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 +# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 +# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +# Issuer: O=certSIGN OU=certSIGN ROOT CA +# Subject: O=certSIGN OU=certSIGN ROOT CA +# Label: "certSIGN ROOT CA" +# Serial: 35210227249154 +# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 +# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b +# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT +AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD +QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP +MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do +0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ +UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d +RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ +OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv +JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C +AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O +BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ +LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY +MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ +44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I +Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw +i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN +9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G3" +# Serial: 28809105769928564313984085209975885599 +# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 +# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd +# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT +MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ +BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 +BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz ++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm +hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn +5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W +JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL +DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC +huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB +AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB +zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN +kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH +SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G +spki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G2" +# Serial: 71758320672825410020661621085256472406 +# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f +# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 +# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp +IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi +BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw +MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig +YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v +dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ +BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 +papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K +DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 +KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox +XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G3" +# Serial: 127614157056681299805556476275995414779 +# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 +# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 +# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB +rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV +BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa +Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl +LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u +MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm +gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 +YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf +b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 +9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S +zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk +OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA +2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW +oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c +KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM +m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu +MdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G2" +# Serial: 80682863203381065782177908751794619243 +# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a +# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 +# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL +MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj +KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 +MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw +NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV +BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH +MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL +So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal +tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG +CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT +qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz +rD6ogRLQy7rQkgu2npaqBA+K +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Universal Root Certification Authority" +# Serial: 85209574734084581917763752644031726877 +# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 +# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 +# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB +vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W +ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 +IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y +IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh +bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF +9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH +H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H +LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN +/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT +rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw +WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs +exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 +sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ +seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz +4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ +BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR +lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 +7M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" +# Serial: 63143484348153506665311985501458640051 +# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 +# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a +# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp +U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg +SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln +biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm +GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve +fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ +aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj +aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW +kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC +4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga +FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" +# Serial: 80544274841616 +# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 +# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 +# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G2" +# Serial: 10000012 +# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a +# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16 +# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f +-----BEGIN CERTIFICATE----- +MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX +DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 +qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp +uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU +Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE +pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp +5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M +UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN +GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy +5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv +6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK +eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 +B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ +BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov +L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG +SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS +CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen +5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 +IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK +gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL ++63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL +vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm +bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk +N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC +Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z +ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Label: "Hongkong Post Root CA 1" +# Serial: 1000 +# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca +# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58 +# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2 +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx +FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg +Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG +A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr +b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ +jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn +PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh +ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9 +nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h +q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED +MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC +mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3 +7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB +oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs +EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO +fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi +AmvZWg== +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Label: "SecureSign RootCA11" +# Serial: 1 +# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 +# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 +# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr +MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG +A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0 +MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp +Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD +QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz +i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8 +h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV +MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9 +UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni +8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC +h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD +VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB +AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm +KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ +X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr +QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 +pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN +QSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Label: "Microsec e-Szigno Root CA 2009" +# Serial: 14014712776195784473 +# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 +# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e +# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Label: "GlobalSign Root CA - R3" +# Serial: 4835703278459759426209954 +# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 +# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad +# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- + +# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" +# Serial: 6047274297262753887 +# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 +# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa +# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy +MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD +VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv +ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl +AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF +661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 +am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 +ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 +PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS +3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k +SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF +3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM +ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g +StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz +Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB +jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +# Issuer: CN=Izenpe.com O=IZENPE S.A. +# Subject: CN=Izenpe.com O=IZENPE S.A. +# Label: "Izenpe.com" +# Serial: 917563065490389241595536686991402621 +# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 +# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 +# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 +MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 +ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD +VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j +b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq +scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO +xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H +LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX +uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD +yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ +JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q +rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN +BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L +hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB +QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ +HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu +Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg +QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB +BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA +A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb +laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 +awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo +JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw +LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT +VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk +LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb +UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ +QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ +naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls +QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +# Issuer: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Label: "Chambers of Commerce Root - 2008" +# Serial: 11806822484801597146 +# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 +# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c +# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz +IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz +MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj +dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw +EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp +MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 +28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq +VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q +DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR +5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL +ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a +Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl +UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s ++12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 +Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj +ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx +hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV +HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 ++HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN +YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t +L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy +ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt +IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV +HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w +DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW +PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF +5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 +glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH +FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 +pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD +xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG +tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq +jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De +fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg +OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ +d0jQ +-----END CERTIFICATE----- + +# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Label: "Global Chambersign Root - 2008" +# Serial: 14541511773111788494 +# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 +# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c +# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca +-----BEGIN CERTIFICATE----- +MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD +aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx +MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy +cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG +A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl +BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed +KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 +G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 +zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 +ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG +HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 +Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V +yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e +beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r +6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh +wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog +zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW +BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr +ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp +ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk +cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt +YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC +CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI +hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ +UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz +X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x +fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz +a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd +Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd +SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O +AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso +M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge +v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z +09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B +-----END CERTIFICATE----- + +# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Label: "Go Daddy Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 +# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b +# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 +# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e +# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Services Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 +# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f +# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Commercial O=AffirmTrust +# Subject: CN=AffirmTrust Commercial O=AffirmTrust +# Label: "AffirmTrust Commercial" +# Serial: 8608355977964138876 +# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 +# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 +# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP +Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr +ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL +MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 +yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr +VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ +nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG +XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj +vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt +Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g +N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC +nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Networking O=AffirmTrust +# Subject: CN=AffirmTrust Networking O=AffirmTrust +# Label: "AffirmTrust Networking" +# Serial: 8957382827206547757 +# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f +# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f +# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y +YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua +kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL +QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp +6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG +yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i +QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO +tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu +QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ +Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u +olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 +x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium O=AffirmTrust +# Subject: CN=AffirmTrust Premium O=AffirmTrust +# Label: "AffirmTrust Premium" +# Serial: 7893706540734352110 +# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 +# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 +# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz +dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG +A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U +cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf +qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ +JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ ++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS +s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 +HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 +70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG +V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S +qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S +5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia +C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX +OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE +FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 +KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B +8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ +MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc +0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ +u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF +u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH +YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 +GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO +RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e +KeC2uAloGRwYQw== +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust +# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust +# Label: "AffirmTrust Premium ECC" +# Serial: 8401224907861490260 +# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d +# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb +# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ +cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ +BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt +VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D +0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 +ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G +A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs +aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I +flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA" +# Serial: 279744 +# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 +# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e +# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Label: "TWCA Root Certification Authority" +# Serial: 1 +# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 +# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 +# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Label: "Security Communication RootCA2" +# Serial: 0 +# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 +# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 +# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2011" +# Serial: 0 +# MD5 Fingerprint: 73:9f:4c:4b:73:5b:79:e9:fa:ba:1c:ef:6e:cb:d5:c9 +# SHA1 Fingerprint: fe:45:65:9b:79:03:5b:98:a1:61:b5:51:2e:ac:da:58:09:48:22:4d +# SHA256 Fingerprint: bc:10:4f:15:a4:8b:e7:09:dc:a5:42:a7:e1:d4:b9:df:6f:05:45:27:e8:02:ea:a9:2d:59:54:44:25:8a:fe:71 +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix +RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p +YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw +NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK +EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl +cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz +dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ +fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns +bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD +75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP +FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV +HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp +5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu +b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA +A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p +6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7 +dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys +Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI +l7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Label: "Actalis Authentication Root CA" +# Serial: 6271844772424770508 +# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 +# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac +# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +# Issuer: O=Trustis Limited OU=Trustis FPS Root CA +# Subject: O=Trustis Limited OU=Trustis FPS Root CA +# Label: "Trustis FPS Root CA" +# Serial: 36053640375399034304724988975563710553 +# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d +# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 +# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL +ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx +MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc +MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ +AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH +iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj +vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA +0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB +OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ +BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E +FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 +GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW +zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 +1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE +f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F +jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN +ZetX2fNXlrtIzYE= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 2 Root CA" +# Serial: 2 +# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 +# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 +# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 3 Root CA" +# Serial: 2 +# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec +# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 +# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 3" +# Serial: 1 +# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef +# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 +# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Label: "EE Certification Centre Root CA" +# Serial: 112324828676200291871926431888494945866 +# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f +# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 +# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 +MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 +czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG +CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy +MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl +ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS +b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy +euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO +bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw +WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d +MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE +1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ +zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB +BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF +BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV +v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG +E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW +iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v +GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 2009" +# Serial: 623603 +# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f +# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 +# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 EV 2009" +# Serial: 623604 +# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 +# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 +# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R2 O=Disig a.s. +# Subject: CN=CA Disig Root R2 O=Disig a.s. +# Label: "CA Disig Root R2" +# Serial: 10572350602393338211 +# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 +# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 +# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Label: "ACCVRAIZ1" +# Serial: 6828503384748696800 +# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 +# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 +# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA Global Root CA" +# Serial: 3262 +# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 +# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 +# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Label: "TeliaSonera Root CA v1" +# Serial: 199041966741090107964904287217786801558 +# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c +# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 +# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +# Issuer: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi +# Subject: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi +# Label: "E-Tugra Certification Authority" +# Serial: 7667447206703254355 +# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 +# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 +# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV +BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC +aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV +BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 +Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz +MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ +BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp +em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY +B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH +D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF +Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo +q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D +k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH +fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut +dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM +ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 +zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX +U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 +Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 +XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF +Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR +HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY +GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c +77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 ++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK +vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 +FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl +yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P +AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD +y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d +NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot 2011 O=Atos +# Subject: CN=Atos TrustedRoot 2011 O=Atos +# Label: "Atos TrustedRoot 2011" +# Serial: 6643877497813316402 +# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 +# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 +# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 14367148294922964480859022125800977897474 +# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e +# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb +# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ +FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F +uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX +kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs +ewv4n4Q= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Label: "GlobalSign ECC Root CA - R5" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G3" +# Serial: 10003001 +# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 +# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc +# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Label: "Staat der Nederlanden EV Root CA" +# Serial: 10000013 +# MD5 Fingerprint: fc:06:af:7b:e8:1a:f1:9a:b4:e8:d2:70:1f:c0:f5:ba +# SHA1 Fingerprint: 76:e2:7e:c1:4f:db:82:c1:c0:a6:75:b5:05:be:3d:29:b4:ed:db:bb +# SHA256 Fingerprint: 4d:24:91:41:4c:fe:95:67:46:ec:4c:ef:a6:cf:6f:72:e2:8a:13:29:43:2f:9d:8a:90:7a:c4:cb:5d:ad:c1:5a +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y +MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg +TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS +b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS +M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC +UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d +Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p +rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l +pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb +j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC +KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS +/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X +cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH +1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP +px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7 +MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u +2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS +v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC +wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy +CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e +vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6 +Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa +Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL +eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 +FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc +7uzXLg== +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Label: "IdenTrust Commercial Root CA 1" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G2" +# Serial: 1246989352 +# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 +# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 +# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - EC1" +# Serial: 51543124481930649114116133369 +# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc +# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 +# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GB CA" +# Serial: 157768595616588414422159278966750757568 +# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d +# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed +# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Label: "SZAFIR ROOT CA2" +# Serial: 357043034767186914217277344587386743377558296292 +# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 +# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de +# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA 2" +# Serial: 44979900017204383099463764357512596969 +# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 +# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 +# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce +# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 +# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef +# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 +# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X1 O=Internet Security Research Group +# Subject: CN=ISRG Root X1 O=Internet Security Research Group +# Label: "ISRG Root X1" +# Serial: 172886928669790476064670243504169061120 +# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e +# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 +# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Label: "AC RAIZ FNMT-RCM" +# Serial: 485876308206448804701554682760554759 +# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d +# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 +# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 1 O=Amazon +# Subject: CN=Amazon Root CA 1 O=Amazon +# Label: "Amazon Root CA 1" +# Serial: 143266978916655856878034712317230054538369994 +# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 +# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 +# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 2 O=Amazon +# Subject: CN=Amazon Root CA 2 O=Amazon +# Label: "Amazon Root CA 2" +# Serial: 143266982885963551818349160658925006970653239 +# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 +# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a +# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 3 O=Amazon +# Subject: CN=Amazon Root CA 3 O=Amazon +# Label: "Amazon Root CA 3" +# Serial: 143266986699090766294700635381230934788665930 +# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 +# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e +# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 4 O=Amazon +# Subject: CN=Amazon Root CA 4 O=Amazon +# Label: "Amazon Root CA 4" +# Serial: 143266989758080763974105200630763877849284878 +# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd +# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be +# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +# Issuer: CN=LuxTrust Global Root 2 O=LuxTrust S.A. +# Subject: CN=LuxTrust Global Root 2 O=LuxTrust S.A. +# Label: "LuxTrust Global Root 2" +# Serial: 59914338225734147123941058376788110305822489521 +# MD5 Fingerprint: b2:e1:09:00:61:af:f7:f1:91:6f:c4:ad:8d:5e:3b:7c +# SHA1 Fingerprint: 1e:0e:56:19:0a:d1:8b:25:98:b2:04:44:ff:66:8a:04:17:99:5f:3f +# SHA256 Fingerprint: 54:45:5f:71:29:c2:0b:14:47:c4:18:f9:97:16:8f:24:c5:8f:c5:02:3b:f5:da:5b:e2:eb:6e:1d:d8:90:2e:d5 +-----BEGIN CERTIFICATE----- +MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQEL +BQAwRjELMAkGA1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNV +BAMMFkx1eFRydXN0IEdsb2JhbCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUw +MzA1MTMyMTU3WjBGMQswCQYDVQQGEwJMVTEWMBQGA1UECgwNTHV4VHJ1c3QgUy5B +LjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xvYmFsIFJvb3QgMjCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNMPIf5U2o3C/IPPIfOb9wmKb3F +ibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJIcRHIbjuend+JZTem +hfY7RBi2xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy95iJMHZP1 +EMShduxq3sVs35a0VkBCwGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsn +Xpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4 +zDRbIvCGp4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkmFRseTJIpgp7VkoGSQXAZ +96Tlk0u8d2cx3Rz9MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niFwpN6cj5m +j5wWEWCPnolvZ77gR1o7DJpni89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4g +DEa/a4ebsypmQjVGbKq6rfmYe+lQVRQxv7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+ +8kPREd8vZS9kzl8UubG/Mb2HeFpZZYiq/FkySIbWTLkpS5XTdvN3JW1CHDiDTf2j +X5t/Lax5Gw5CMZdjpPuKadUiDTSQMC6otOBttpSsvItO13D8xTiOZCXhTTmQzsmH +hFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB/zBCBgNVHSAEOzA5MDcGByuB +KwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5Lmx1eHRydXN0 +Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT ++Et8szAdBgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQEL +BQADggIBAGoZFO1uecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9 +BzZAcg4atmpZ1gDlaCDdLnINH2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTO +jFu1EVzPig4N1qx3gf4ynCSecs5U89BvolbW7MM3LGVYvlcAGvI1+ut7MV3CwRI9 +loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaXcozrhAIuZY+kt9J/Z93I055c +qqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8wkbIEa91WvpWAVWe+ +2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx9xIX3eP/ +JEAdemrRTxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKre +zrnK+T+Tb/mjuuqlPpmt/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQf +LSoSOcbDWjLtR5EWDrw4wVDej8oqkDQc7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+ +x9CWttrhSmQGbmBNvUJO/3jaJMobtNeWOWyu8Q6qp31IiyBMz2TWuJdGsE7RKlY6 +oJO9r4Ak4Ap+58rVyuiFVdw2KuGUaJPHZnJED4AhMmwlxyOAgwrr +-----END CERTIFICATE----- + +# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" +# Serial: 1 +# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 +# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca +# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Label: "GDCA TrustAUTH R5 ROOT" +# Serial: 9009899650740120186 +# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 +# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 +# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE +BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 +MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w +HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj +Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj +TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u +KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj +qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm +MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 +ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP +zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk +L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC +jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA +HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC +AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm +DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 +COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry +L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf +JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg +IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io +2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV +09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ +XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq +T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe +MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +# Issuer: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Subject: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Label: "TrustCor RootCert CA-1" +# Serial: 15752444095811006489 +# MD5 Fingerprint: 6e:85:f1:dc:1a:00:d3:22:d5:b2:b2:ac:6b:37:05:45 +# SHA1 Fingerprint: ff:bd:cd:e7:82:c8:43:5e:3c:6f:26:86:5c:ca:a8:3a:45:5b:c3:0a +# SHA256 Fingerprint: d4:0e:9c:86:cd:8f:e4:68:c1:77:69:59:f4:9e:a7:74:fa:54:86:84:b6:c4:06:f3:90:92:61:f4:dc:e2:57:5c +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYD +VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk +MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29y +IFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkxMjMxMTcyMzE2WjCB +pDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFuYW1h +IENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUG +A1UECwweVHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZU +cnVzdENvciBSb290Q2VydCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAv463leLCJhJrMxnHQFgKq1mqjQCj/IDHUHuO1CAmujIS2CNUSSUQIpid +RtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4pQa81QBeCQryJ3pS/C3V +seq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0JEsq1pme +9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CV +EY4hgLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorW +hnAbJN7+KIor0Gqw/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/ +DeOxCbeKyKsZn3MzUOcwHwYDVR0jBBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5mDo4Nvu7Zp5I +/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf +ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZ +yonnMlo2HD6CqFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djts +L1Ac59v2Z3kf9YKVmgenFK+P3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdN +zl/HHk484IkzlQsPpTLWPFp5LBk= +-----END CERTIFICATE----- + +# Issuer: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Subject: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Label: "TrustCor RootCert CA-2" +# Serial: 2711694510199101698 +# MD5 Fingerprint: a2:e1:f8:18:0b:ba:45:d5:c7:41:2a:bb:37:52:45:64 +# SHA1 Fingerprint: b8:be:6d:cb:56:f1:55:b9:63:d4:12:ca:4e:06:34:c7:94:b2:1c:c0 +# SHA256 Fingerprint: 07:53:e9:40:37:8c:1b:d5:e3:83:6e:39:5d:ae:a5:cb:83:9e:50:46:f1:bd:0e:ae:19:51:cf:10:fe:c7:c9:65 +-----BEGIN CERTIFICATE----- +MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNV +BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw +IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy +dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEfMB0GA1UEAwwWVHJ1c3RDb3Ig +Um9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEyMzExNzI2MzlaMIGk +MQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEg +Q2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYD +VQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRy +dXN0Q29yIFJvb3RDZXJ0IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCnIG7CKqJiJJWQdsg4foDSq8GbZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+ +QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9NkRvRUqdw6VC0xK5mC8tkq +1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1oYxOdqHp +2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nK +DOObXUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hape +az6LMvYHL1cEksr1/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF +3wP+TfSvPd9cW436cOGlfifHhi5qjxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88 +oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQPeSghYA2FFn3XVDjxklb9tTNM +g9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+CtgrKAmrhQhJ8Z3 +mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh +8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAd +BgNVHQ4EFgQU2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6U +nrybPZx9mCAZ5YwwYrIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/hOsh80QA9z+LqBrWyOrsGS2h60COX +dKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnpkpfbsEZC89NiqpX+ +MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv2wnL +/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RX +CI/hOWB3S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYa +ZH9bDTMJBzN7Bj8RpFxwPIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW +2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dvDDqPys/cA8GiCcjl/YBeyGBCARsaU1q7 +N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYURpFHmygk71dSTlxCnKr3 +Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANExdqtvArB +As8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp +5KeXRKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu +1uwJ +-----END CERTIFICATE----- + +# Issuer: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Subject: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Label: "TrustCor ECA-1" +# Serial: 9548242946988625984 +# MD5 Fingerprint: 27:92:23:1d:0a:f5:40:7c:e9:e6:6b:9d:d8:f5:e7:6c +# SHA1 Fingerprint: 58:d1:df:95:95:67:6b:63:c0:f0:5b:1c:17:4d:8b:84:0b:c8:78:bd +# SHA256 Fingerprint: 5a:88:5d:b1:9c:01:d9:12:c5:75:93:88:93:8c:af:bb:df:03:1a:b2:d4:8e:91:ee:15:58:9b:42:97:1d:03:9c +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYD +VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk +MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDlRydXN0Q29y +IEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3MjgwN1owgZwxCzAJBgNV +BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw +IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy +dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3Ig +RUNBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb +3w9U73NjKYKtR8aja+3+XzP4Q1HpGjORMRegdMTUpwHmspI+ap3tDvl0mEDTPwOA +BoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23xFUfJ3zSCNV2HykVh0A5 +3ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmcp0yJF4Ou +owReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/ +wZ0+fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZF +ZtS6mFjBAgMBAAGjYzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAf +BgNVHSMEGDAWgBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEABT41XBVwm8nHc2Fv +civUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u/ukZMjgDfxT2 +AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F +hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50 +soIipX1TH0XsJ5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BI +WJZpTdwHjFGTot+fDz2LYLSCjaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1Wi +tJ/X5g== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Label: "SSL.com Root Certification Authority RSA" +# Serial: 8875640296558310041 +# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 +# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb +# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com Root Certification Authority ECC" +# Serial: 8495723813297216424 +# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e +# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a +# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority RSA R2" +# Serial: 6248227494352943350 +# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 +# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a +# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority ECC" +# Serial: 3182246526754555285 +# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 +# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d +# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Label: "GlobalSign Root CA - R6" +# Serial: 1417766617973444989252670301619537 +# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae +# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 +# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GC CA" +# Serial: 44084345621038548146064804565436152554 +# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 +# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 +# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R1 O=Google Trust Services LLC +# Subject: CN=GTS Root R1 O=Google Trust Services LLC +# Label: "GTS Root R1" +# Serial: 146587175971765017618439757810265552097 +# MD5 Fingerprint: 82:1a:ef:d4:d2:4a:f2:9f:e2:3d:97:06:14:70:72:85 +# SHA1 Fingerprint: e1:c9:50:e6:ef:22:f8:4c:56:45:72:8b:92:20:60:d7:d5:a7:a3:e8 +# SHA256 Fingerprint: 2a:57:54:71:e3:13:40:bc:21:58:1c:bd:2c:f1:3e:15:84:63:20:3e:ce:94:bc:f9:d3:cc:19:6b:f0:9a:54:72 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH +MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM +QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy +MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl +cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM +f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX +mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7 +zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P +fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc +vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4 +Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp +zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO +Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW +k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+ +DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF +lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW +Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1 +d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z +XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR +gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3 +d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv +J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg +DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM ++SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy +F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9 +SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws +E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R2 O=Google Trust Services LLC +# Subject: CN=GTS Root R2 O=Google Trust Services LLC +# Label: "GTS Root R2" +# Serial: 146587176055767053814479386953112547951 +# MD5 Fingerprint: 44:ed:9a:0e:a4:09:3b:00:f2:ae:4c:a3:c6:61:b0:8b +# SHA1 Fingerprint: d2:73:96:2a:2a:5e:39:9f:73:3f:e1:c7:1e:64:3f:03:38:34:fc:4d +# SHA256 Fingerprint: c4:5d:7b:b0:8e:6d:67:e6:2e:42:35:11:0b:56:4e:5f:78:fd:92:ef:05:8c:84:0a:ea:4e:64:55:d7:58:5c:60 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH +MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM +QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy +MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl +cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv +CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg +GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu +XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd +re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu +PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1 +mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K +8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj +x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR +nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0 +kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok +twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp +8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT +vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT +z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA +pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb +pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB +R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R +RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk +0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC +5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF +izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn +yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R3 O=Google Trust Services LLC +# Subject: CN=GTS Root R3 O=Google Trust Services LLC +# Label: "GTS Root R3" +# Serial: 146587176140553309517047991083707763997 +# MD5 Fingerprint: 1a:79:5b:6b:04:52:9c:5d:c7:74:33:1b:25:9a:f9:25 +# SHA1 Fingerprint: 30:d4:24:6f:07:ff:db:91:89:8a:0b:e9:49:66:11:eb:8c:5e:46:e5 +# SHA256 Fingerprint: 15:d5:b8:77:46:19:ea:7d:54:ce:1c:a6:d0:b0:c4:03:e0:37:a9:17:f1:31:e8:a0:4e:1e:6b:7a:71:ba:bc:e5 +-----BEGIN CERTIFICATE----- +MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout +736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A +DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk +fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA +njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R4 O=Google Trust Services LLC +# Subject: CN=GTS Root R4 O=Google Trust Services LLC +# Label: "GTS Root R4" +# Serial: 146587176229350439916519468929765261721 +# MD5 Fingerprint: 5d:b6:6a:c4:60:17:24:6a:1a:99:a8:4b:ee:5e:b4:26 +# SHA1 Fingerprint: 2a:1d:60:27:d9:4a:b1:0a:1c:4d:91:5c:cd:33:a0:cb:3e:2d:54:cb +# SHA256 Fingerprint: 71:cc:a5:39:1f:9e:79:4b:04:80:25:30:b3:63:e1:21:da:8a:30:43:bb:26:66:2f:ea:4d:ca:7f:c9:51:a4:bd +-----BEGIN CERTIFICATE----- +MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu +hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l +xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0 +CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx +sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w== +-----END CERTIFICATE----- + +# Issuer: CN=UCA Global G2 Root O=UniTrust +# Subject: CN=UCA Global G2 Root O=UniTrust +# Label: "UCA Global G2 Root" +# Serial: 124779693093741543919145257850076631279 +# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8 +# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a +# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH +bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x +CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds +b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr +b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9 +kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm +VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R +VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc +C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj +tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY +D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv +j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl +NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6 +iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP +O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV +ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj +L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl +1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU +b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV +PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj +y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb +EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg +DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI ++Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy +YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX +UB+K+wb1whnw0A== +-----END CERTIFICATE----- + +# Issuer: CN=UCA Extended Validation Root O=UniTrust +# Subject: CN=UCA Extended Validation Root O=UniTrust +# Label: "UCA Extended Validation Root" +# Serial: 106100277556486529736699587978573607008 +# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2 +# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a +# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF +eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx +MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV +BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog +D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS +sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop +O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk +sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi +c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj +VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz +KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/ +TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G +sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs +1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD +fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN +l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ +VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5 +c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp +4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s +t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj +2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO +vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C +xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx +cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM +fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax +-----END CERTIFICATE----- + +# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Label: "Certigna Root CA" +# Serial: 269714418870597844693661054334862075617 +# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77 +# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43 +# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68 +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign Root CA - G1" +# Serial: 235931866688319308814040 +# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac +# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c +# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67 +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign ECC Root CA - G3" +# Serial: 287880440101571086945156 +# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 +# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 +# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Label: "emSign Root CA - C1" +# Serial: 825510296613316004955058 +# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68 +# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01 +# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG +A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg +SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v +dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ +BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ +HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH +3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH +GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c +xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 +aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq +TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 +/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 +kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG +YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT ++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo +WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Label: "emSign ECC Root CA - C3" +# Serial: 582948710642506000014504 +# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5 +# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66 +# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3 +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG +EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx +IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND +IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci +MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti +sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O +BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c +3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J +0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Label: "Hongkong Post Root CA 3" +# Serial: 46170865288971385588281144162979347873371282084 +# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0 +# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02 +# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6 +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ +SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n +a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5 +NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT +CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u +Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO +dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI +VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV +9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY +2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY +vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt +bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb +x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+ +l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK +TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj +Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw +DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG +7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk +MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr +gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk +GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS +3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm +Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+ +l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c +JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP +L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa +LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG +mpv0 +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G4" +# Serial: 289383649854506086828220374796556676440 +# MD5 Fingerprint: 89:53:f1:83:23:b7:7c:8e:05:f1:8c:71:38:4e:1f:88 +# SHA1 Fingerprint: 14:88:4e:86:26:37:b0:26:af:59:62:5c:40:77:ec:35:29:ba:96:01 +# SHA256 Fingerprint: db:35:17:d1:f6:73:2a:2d:5a:b9:7c:53:3e:c7:07:79:ee:32:70:a6:2f:b4:ac:42:38:37:24:60:e6:f0:1e:88 +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw +gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL +Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg +MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw +BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0 +MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1 +c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ +bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ +2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E +T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j +5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM +C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T +DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX +wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A +2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm +nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl +N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj +c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS +5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS +Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr +hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/ +B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI +AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw +H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+ +b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk +2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol +IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk +5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY +n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- diff --git a/robot/lib/python3.8/site-packages/certifi/core.py b/robot/lib/python3.8/site-packages/certifi/core.py new file mode 100644 index 0000000000000000000000000000000000000000..53404e133ff5a22e80bb3d87e37cac0d172c498e --- /dev/null +++ b/robot/lib/python3.8/site-packages/certifi/core.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +""" +certifi.py +~~~~~~~~~~ + +This module returns the installation location of cacert.pem. +""" +import os + + +def where(): + f = os.path.dirname(__file__) + + return '/etc/ssl/certs/ca-certificates.crt' diff --git a/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/AUTHORS.txt b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/AUTHORS.txt new file mode 100644 index 0000000000000000000000000000000000000000..72c87d7d38ae7bf859717c333a5ee8230f6ce624 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/AUTHORS.txt @@ -0,0 +1,562 @@ +A_Rog +Aakanksha Agrawal <11389424+rasponic@users.noreply.github.com> +Abhinav Sagar <40603139+abhinavsagar@users.noreply.github.com> +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Tse +Adam Wentz +admin +Adrien Morison +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Aleks Bunin +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Alexey Popravka +Alli +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrey Bulgakov +Andrés Delfino <34587441+andresdelfino@users.noreply.github.com> +Andrés Delfino +Andy Freeland +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anuj Godase +AQNOUCH Mohammed +AraHaan +Arindam Choudhury +Armin Ronacher +Artem +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avner Cohen +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Darnell +Ben Hoyt +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Brandon L. Reiss +Brandt Bucher +Brett Randall +Brian Cristante <33549821+brcrista@users.noreply.github.com> +Brian Cristante +Brian Rosner +BrownTruck +Bruno Oliveira +Bruno Renié +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +c22 +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Chih-Hsuan Yen +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris McDonough +Chris Wolfe +Christian Heimes +Christian Oudard +Christopher Hunt +Christopher Snyder +Clark Boylan +Clay McClure +Cody +Cody Soyland +Colin Watson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Curtis Doty +cytolentino +Damian Quiroga +Dan Black +Dan Savilonis +Dan Sully +daniel +Daniel Collins +Daniel Hahler +Daniel Holth +Daniel Jost +Daniel Shaulov +Daniele Esposti +Daniele Procida +Danny Hermes +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Bordeynik +David Caro +David Evans +David Linke +David Pursehouse +David Tucker +David Wales +Davidovich +derwolfe +Desetude +Diego Caraballo +DiegoCaraballo +Dmitry Gladkov +Domen Kožar +Donald Stufft +Dongweiming +Douglas Thor +DrFeathers +Dustin Ingram +Dwayne Bailey +Ed Morley <501702+edmorley@users.noreply.github.com> +Ed Morley +Eitan Adler +ekristina +elainechan +Eli Schwartz +Eli Schwartz +Emil Burzo +Emil Styrke +Endoh Takanao +enoch +Erdinc Mutlu +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Ernest W Durbin III +Ernest W. Durbin III +Erwin Janssen +Eugene Vereshchagin +everdimension +Felix Yan +fiber-space +Filip Kokosiński +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gdanielson +Geoffrey Lehée +Geoffrey Sneddon +George Song +Georgi Valkov +Giftlin Rajaiah +gizmoguy1 +gkdoc <40815324+gkdoc@users.noreply.github.com> +Gopinath M <31352222+mgopi1990@users.noreply.github.com> +GOTO Hayato <3532528+gh640@users.noreply.github.com> +gpiks +Guilherme Espada +Guy Rozendorn +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +Herbert Pfennig +Hsiaoming Yang +Hugo +Hugo Lopes Tavares +Hugo van Kemenade +hugovk +Hynek Schlawack +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ilya Baryshev +INADA Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Ivan Pozdeev +Jacob Kim +jakirkham +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James Cleveland +James Cleveland +James Firth +James Polley +Jan Pokorný +Jannis Leidel +jarondl +Jason R. Coombs +Jay Graves +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jelmer Vernooij +jenix21 +Jeremy Stanley +Jeremy Zafran +Jiashuo Li +Jim Garrison +Jivan Amara +John Paton +John-Scott Atlakson +johnthagen +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joost Molenaar +Jorge Niedbalski +Joseph Long +Josh Bronson +Josh Hansen +Josh Schneier +Juanjo Bazán +Julian Berman +Julian Gethmann +Julien Demoor +jwg4 +Jyrki Pulliainen +Kai Chen +Kamal Bin Mustafa +kaustav haldar +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kexuan Sun +Kit Randel +kpinc +Krishna Oza +Kumar McMillan +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurie Opperman +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +Loren Carvalho +Lucas Cimon +Ludovic Gasc +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +László Kiss Kollár +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark Williams +Mark Williams +Markus Hametner +Masaki +Masklinn +Matej Stuchlik +Mathew Jennings +Mathieu Bridon +Matt Good +Matt Maker +Matt Robenolt +matthew +Matthew Einhorn +Matthew Gilliard +Matthew Iversen +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna <44498973+mbaluna@users.noreply.github.com> +mdebi <17590103+mdebi@users.noreply.github.com> +memoselyk +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Williamson +michaelpacer +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Monica Baluna +montefra +Monty Taylor +Nate Coraor +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nikhil Benesch +Nitesh Sharma +Nowell Strite +NtaleGrey +nvdv +Ofekmeister +ofrinevo +Oliver Jeeves +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +OMOTO Kenji +Omry Yadan +Oren Held +Oscar Benjamin +Oz N Tiram +Pachwenko <32424503+Pachwenko@users.noreply.github.com> +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavithra Eswaramoorthy <33131404+QueenCoffee@users.noreply.github.com> +Pawel Jasinski +Pekka Klärck +Peter Lisák +Peter Waller +petr-tik +Phaneendra Chiruvella +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Pratik Mallya +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal <7895pulkit@gmail.com> +Qiangning Hong +Quentin Pradet +R. David Murray +Rafael Caricio +Ralf Schmitt +Razzi Abuissa +rdb +Remi Rampin +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Richard Jones +RobberPhex +Robert Collins +Robert McGibbon +Robert T. McGibbon +robin elisha robinson +Roey Berman +Rohan Jain +Rohan Jain +Rohan Jain +Roman Bogorodskiy +Romuald Brunet +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +Roy Wellington Ⅳ +Ryan Wooden +ryneeverett +Sachi King +Salvatore Rinchiera +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sergey Vasilyev +Seth Woodworth +Shlomi Fish +Shovan Maity +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +Sorin Sbarnea +Stavros Korokithakis +Stefan Scherfke +Stephan Erb +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +stonebig +Stéphane Bidoul (ACSONE) +Stéphane Bidoul +Stéphane Klein +Sumana Harihareswara +Sviatoslav Sydorenko +Sviatoslav Sydorenko +Swat009 +Takayuki SHIMIZUKAWA +tbeswick +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tom Forbes +Tom Freudenheim +Tom V +Tomas Orsava +Tomer Chachamu +Tony Beswick +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +Travis Swicegood +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Philippon +Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> +Vitaly Babiy +Vladimir Rutsky +W. Trevor King +Wil Tan +Wilfred Hughes +William ML Leslie +William T Olson +Wilson Mo +wim glenn +Wolfgang Maier +Xavier Fernandez +Xavier Fernandez +xoviat +xtreak +YAMAMOTO Takashi +Yen Chi Hsuan +Yeray Diaz Diaz +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Zearin +Zearin +Zhiping Deng +Zvezdan Petkovic +Łukasz Langa +Семён Марьясин diff --git a/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..737fec5c5352af3d9a6a47a0670da4bdb52c5725 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2019 The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/METADATA b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..f1f13a85571a799a7fd2d62cc1b96b0e0689d052 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/METADATA @@ -0,0 +1,98 @@ +Metadata-Version: 2.1 +Name: chardet +Version: 3.0.4 +Summary: Universal encoding detector for Python 2 and 3 +Home-page: https://github.com/chardet/chardet +Author: Mark Pilgrim +Author-email: mark@diveintomark.org +Maintainer: Daniel Blanchard +Maintainer-email: dan.blanchard@gmail.com +License: LGPL +Keywords: encoding,i18n,xml +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Text Processing :: Linguistic + +Chardet: The Universal Character Encoding Detector +-------------------------------------------------- + +.. image:: https://img.shields.io/travis/chardet/chardet/stable.svg + :alt: Build status + :target: https://travis-ci.org/chardet/chardet + +.. image:: https://img.shields.io/coveralls/chardet/chardet/stable.svg + :target: https://coveralls.io/r/chardet/chardet + +.. image:: https://img.shields.io/pypi/v/chardet.svg + :target: https://warehouse.python.org/project/chardet/ + :alt: Latest version on PyPI + +.. image:: https://img.shields.io/pypi/l/chardet.svg + :alt: License + + +Detects + - ASCII, UTF-8, UTF-16 (2 variants), UTF-32 (4 variants) + - Big5, GB2312, EUC-TW, HZ-GB-2312, ISO-2022-CN (Traditional and Simplified Chinese) + - EUC-JP, SHIFT_JIS, CP932, ISO-2022-JP (Japanese) + - EUC-KR, ISO-2022-KR (Korean) + - KOI8-R, MacCyrillic, IBM855, IBM866, ISO-8859-5, windows-1251 (Cyrillic) + - ISO-8859-5, windows-1251 (Bulgarian) + - ISO-8859-1, windows-1252 (Western European languages) + - ISO-8859-7, windows-1253 (Greek) + - ISO-8859-8, windows-1255 (Visual and Logical Hebrew) + - TIS-620 (Thai) + +.. note:: + Our ISO-8859-2 and windows-1250 (Hungarian) probers have been temporarily + disabled until we can retrain the models. + +Requires Python 2.6, 2.7, or 3.3+. + +Installation +------------ + +Install from `PyPI `_:: + + pip install chardet + +Documentation +------------- + +For users, docs are now available at https://chardet.readthedocs.io/. + +Command-line Tool +----------------- + +chardet comes with a command-line script which reports on the encodings of one +or more files:: + + % chardetect somefile someotherfile + somefile: windows-1252 with confidence 0.5 + someotherfile: ascii with confidence 1.0 + +About +----- + +This is a continuation of Mark Pilgrim's excellent chardet. Previously, two +versions needed to be maintained: one that supported python 2.x and one that +supported python 3.x. We've recently merged with `Ian Cordasco `_'s +`charade `_ fork, so now we have one +coherent version that works for Python 2.6+. + +:maintainer: Dan Blanchard + + diff --git a/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/RECORD b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..78e7b27d9f5710dcefea2d79b03d3082dea45a95 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/RECORD @@ -0,0 +1,97 @@ +chardet/__init__.py,sha256=YsP5wQlsHJ2auF1RZJfypiSrCA7_bQiRm3ES_NI76-Y,1559 +chardet/big5freq.py,sha256=D_zK5GyzoVsRes0HkLJziltFQX0bKCLOrFe9_xDvO_8,31254 +chardet/big5prober.py,sha256=kBxHbdetBpPe7xrlb-e990iot64g_eGSLd32lB7_h3M,1757 +chardet/chardistribution.py,sha256=3woWS62KrGooKyqz4zQSnjFbJpa6V7g02daAibTwcl8,9411 +chardet/charsetgroupprober.py,sha256=6bDu8YIiRuScX4ca9Igb0U69TA2PGXXDej6Cc4_9kO4,3787 +chardet/charsetprober.py,sha256=KSmwJErjypyj0bRZmC5F5eM7c8YQgLYIjZXintZNstg,5110 +chardet/codingstatemachine.py,sha256=VYp_6cyyki5sHgXDSZnXW4q1oelHc3cu9AyQTX7uug8,3590 +chardet/compat.py,sha256=PKTzHkSbtbHDqS9PyujMbX74q1a8mMpeQTDVsQhZMRw,1134 +chardet/cp949prober.py,sha256=TZ434QX8zzBsnUvL_8wm4AQVTZ2ZkqEEQL_lNw9f9ow,1855 +chardet/enums.py,sha256=Aimwdb9as1dJKZaFNUH2OhWIVBVd6ZkJJ_WK5sNY8cU,1661 +chardet/escprober.py,sha256=kkyqVg1Yw3DIOAMJ2bdlyQgUFQhuHAW8dUGskToNWSc,3950 +chardet/escsm.py,sha256=RuXlgNvTIDarndvllNCk5WZBIpdCxQ0kcd9EAuxUh84,10510 +chardet/eucjpprober.py,sha256=iD8Jdp0ISRjgjiVN7f0e8xGeQJ5GM2oeZ1dA8nbSeUw,3749 +chardet/euckrfreq.py,sha256=-7GdmvgWez4-eO4SuXpa7tBiDi5vRXQ8WvdFAzVaSfo,13546 +chardet/euckrprober.py,sha256=MqFMTQXxW4HbzIpZ9lKDHB3GN8SP4yiHenTmf8g_PxY,1748 +chardet/euctwfreq.py,sha256=No1WyduFOgB5VITUA7PLyC5oJRNzRyMbBxaKI1l16MA,31621 +chardet/euctwprober.py,sha256=13p6EP4yRaxqnP4iHtxHOJ6R2zxHq1_m8hTRjzVZ95c,1747 +chardet/gb2312freq.py,sha256=JX8lsweKLmnCwmk8UHEQsLgkr_rP_kEbvivC4qPOrlc,20715 +chardet/gb2312prober.py,sha256=gGvIWi9WhDjE-xQXHvNIyrnLvEbMAYgyUSZ65HUfylw,1754 +chardet/hebrewprober.py,sha256=c3SZ-K7hvyzGY6JRAZxJgwJ_sUS9k0WYkvMY00YBYFo,13838 +chardet/jisfreq.py,sha256=vpmJv2Bu0J8gnMVRPHMFefTRvo_ha1mryLig8CBwgOg,25777 +chardet/jpcntx.py,sha256=PYlNqRUQT8LM3cT5FmHGP0iiscFlTWED92MALvBungo,19643 +chardet/langbulgarianmodel.py,sha256=1HqQS9Pbtnj1xQgxitJMvw8X6kKr5OockNCZWfEQrPE,12839 +chardet/langcyrillicmodel.py,sha256=LODajvsetH87yYDDQKA2CULXUH87tI223dhfjh9Zx9c,17948 +chardet/langgreekmodel.py,sha256=8YAW7bU8YwSJap0kIJSbPMw1BEqzGjWzqcqf0WgUKAA,12688 +chardet/langhebrewmodel.py,sha256=JSnqmE5E62tDLTPTvLpQsg5gOMO4PbdWRvV7Avkc0HA,11345 +chardet/langhungarianmodel.py,sha256=RhapYSG5l0ZaO-VV4Fan5sW0WRGQqhwBM61yx3yxyOA,12592 +chardet/langthaimodel.py,sha256=8l0173Gu_W6G8mxmQOTEF4ls2YdE7FxWf3QkSxEGXJQ,11290 +chardet/langturkishmodel.py,sha256=W22eRNJsqI6uWAfwXSKVWWnCerYqrI8dZQTm_M0lRFk,11102 +chardet/latin1prober.py,sha256=S2IoORhFk39FEFOlSFWtgVybRiP6h7BlLldHVclNkU8,5370 +chardet/mbcharsetprober.py,sha256=AR95eFH9vuqSfvLQZN-L5ijea25NOBCoXqw8s5O9xLQ,3413 +chardet/mbcsgroupprober.py,sha256=h6TRnnYq2OxG1WdD5JOyxcdVpn7dG0q-vB8nWr5mbh4,2012 +chardet/mbcssm.py,sha256=SY32wVIF3HzcjY3BaEspy9metbNSKxIIB0RKPn7tjpI,25481 +chardet/sbcharsetprober.py,sha256=LDSpCldDCFlYwUkGkwD2oFxLlPWIWXT09akH_2PiY74,5657 +chardet/sbcsgroupprober.py,sha256=1IprcCB_k1qfmnxGC6MBbxELlKqD3scW6S8YIwdeyXA,3546 +chardet/sjisprober.py,sha256=IIt-lZj0WJqK4rmUZzKZP4GJlE8KUEtFYVuY96ek5MQ,3774 +chardet/universaldetector.py,sha256=qL0174lSZE442eB21nnktT9_VcAye07laFWUeUrjttY,12485 +chardet/utf8prober.py,sha256=IdD8v3zWOsB8OLiyPi-y_fqwipRFxV9Nc1eKBLSuIEw,2766 +chardet/version.py,sha256=sp3B08mrDXB-pf3K9fqJ_zeDHOCLC8RrngQyDFap_7g,242 +chardet/cli/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +chardet/cli/chardetect.py,sha256=YBO8L4mXo0WR6_-Fjh_8QxPBoEBNqB9oNxNrdc54AQs,2738 +chardet-3.0.4.dist-info/AUTHORS.txt,sha256=RtqU9KfonVGhI48DAA4-yTOBUhBtQTjFhaDzHoyh7uU,21518 +chardet-3.0.4.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090 +chardet-3.0.4.dist-info/METADATA,sha256=o6XNN41EUioeDnklH1-8haOSjI60AkAaI823ANFkOM4,3304 +chardet-3.0.4.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +chardet-3.0.4.dist-info/entry_points.txt,sha256=fAMmhu5eJ-zAJ-smfqQwRClQ3-nozOCmvJ6-E8lgGJo,60 +chardet-3.0.4.dist-info/top_level.txt,sha256=AowzBbZy4x8EirABDdJSLJZMkJ_53iIag8xfKR6D7kI,8 +chardet-3.0.4.dist-info/RECORD,, +chardet/latin1prober.cpython-38.pyc,, +chardet/mbcharsetprober.cpython-38.pyc,, +chardet/compat.cpython-38.pyc,, +chardet/euckrfreq.cpython-38.pyc,, +chardet/sbcharsetprober.cpython-38.pyc,, +chardet/codingstatemachine.cpython-38.pyc,, +../../../bin/chardetect-3.8,, +chardet/escsm.cpython-38.pyc,, +chardet/eucjpprober.cpython-38.pyc,, +chardet/euckrprober.cpython-38.pyc,, +chardet/gb2312prober.cpython-38.pyc,, +chardet/big5prober.cpython-38.pyc,, +chardet/utf8prober.cpython-38.pyc,, +chardet/langcyrillicmodel.cpython-38.pyc,, +chardet/jpcntx.cpython-38.pyc,, +chardet/cli/__init__.cpython-38.pyc,, +chardet/version.cpython-38.pyc,, +chardet/mbcssm.cpython-38.pyc,, +chardet/charsetprober.cpython-38.pyc,, +chardet/langturkishmodel.cpython-38.pyc,, +chardet/cli/chardetect.cpython-38.pyc,, +chardet/euctwfreq.cpython-38.pyc,, +../../../bin/chardetect,, +chardet/langthaimodel.cpython-38.pyc,, +chardet/langhungarianmodel.cpython-38.pyc,, +chardet/langhebrewmodel.cpython-38.pyc,, +chardet/mbcsgroupprober.cpython-38.pyc,, +chardet/cli/__pycache__,, +chardet/universaldetector.cpython-38.pyc,, +chardet/gb2312freq.cpython-38.pyc,, +chardet/chardistribution.cpython-38.pyc,, +chardet/jisfreq.cpython-38.pyc,, +chardet/langbulgarianmodel.cpython-38.pyc,, +chardet/langgreekmodel.cpython-38.pyc,, +chardet-3.0.4.dist-info/__pycache__,, +chardet/big5freq.cpython-38.pyc,, +chardet/enums.cpython-38.pyc,, +chardet/euctwprober.cpython-38.pyc,, +chardet/hebrewprober.cpython-38.pyc,, +chardet/__init__.cpython-38.pyc,, +../../../bin/chardetect3,, +chardet/escprober.cpython-38.pyc,, +chardet/sjisprober.cpython-38.pyc,, +chardet-3.0.4.virtualenv,, +chardet-3.0.4.dist-info/INSTALLER,, +chardet/sbcsgroupprober.cpython-38.pyc,, +chardet/charsetgroupprober.cpython-38.pyc,, +chardet/cp949prober.cpython-38.pyc,, +chardet/__pycache__,, \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/WHEEL b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ef99c6cf3283b50a273ac4c6d009a0aa85597070 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/entry_points.txt b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..a884269e7fc0d5f0cd96ba63c8d473c6560264b6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +chardetect = chardet.cli.chardetect:main + diff --git a/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..79236f25cda563eb57cd7b5838256d9f3fdf18ab --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet-3.0.4.dist-info/top_level.txt @@ -0,0 +1 @@ +chardet diff --git a/robot/lib/python3.8/site-packages/chardet-3.0.4.virtualenv b/robot/lib/python3.8/site-packages/chardet-3.0.4.virtualenv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/chardet/__init__.py b/robot/lib/python3.8/site-packages/chardet/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0f9f820ef6e5f21042efd0e9dc18d097388d7207 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/__init__.py @@ -0,0 +1,39 @@ +######################## BEGIN LICENSE BLOCK ######################## +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + + +from .compat import PY2, PY3 +from .universaldetector import UniversalDetector +from .version import __version__, VERSION + + +def detect(byte_str): + """ + Detect the encoding of the given byte string. + + :param byte_str: The byte sequence to examine. + :type byte_str: ``bytes`` or ``bytearray`` + """ + if not isinstance(byte_str, bytearray): + if not isinstance(byte_str, bytes): + raise TypeError('Expected object of type bytes or bytearray, got: ' + '{0}'.format(type(byte_str))) + else: + byte_str = bytearray(byte_str) + detector = UniversalDetector() + detector.feed(byte_str) + return detector.close() diff --git a/robot/lib/python3.8/site-packages/chardet/big5freq.py b/robot/lib/python3.8/site-packages/chardet/big5freq.py new file mode 100644 index 0000000000000000000000000000000000000000..38f32517aa8f6cf5970f7ceddd1a415289184c3e --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/big5freq.py @@ -0,0 +1,386 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# Big5 frequency table +# by Taiwan's Mandarin Promotion Council +# +# +# 128 --> 0.42261 +# 256 --> 0.57851 +# 512 --> 0.74851 +# 1024 --> 0.89384 +# 2048 --> 0.97583 +# +# Ideal Distribution Ratio = 0.74851/(1-0.74851) =2.98 +# Random Distribution Ration = 512/(5401-512)=0.105 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR + +BIG5_TYPICAL_DISTRIBUTION_RATIO = 0.75 + +#Char to FreqOrder table +BIG5_TABLE_SIZE = 5376 + +BIG5_CHAR_TO_FREQ_ORDER = ( + 1,1801,1506, 255,1431, 198, 9, 82, 6,5008, 177, 202,3681,1256,2821, 110, # 16 +3814, 33,3274, 261, 76, 44,2114, 16,2946,2187,1176, 659,3971, 26,3451,2653, # 32 +1198,3972,3350,4202, 410,2215, 302, 590, 361,1964, 8, 204, 58,4510,5009,1932, # 48 + 63,5010,5011, 317,1614, 75, 222, 159,4203,2417,1480,5012,3555,3091, 224,2822, # 64 +3682, 3, 10,3973,1471, 29,2787,1135,2866,1940, 873, 130,3275,1123, 312,5013, # 80 +4511,2052, 507, 252, 682,5014, 142,1915, 124, 206,2947, 34,3556,3204, 64, 604, # 96 +5015,2501,1977,1978, 155,1991, 645, 641,1606,5016,3452, 337, 72, 406,5017, 80, # 112 + 630, 238,3205,1509, 263, 939,1092,2654, 756,1440,1094,3453, 449, 69,2987, 591, # 128 + 179,2096, 471, 115,2035,1844, 60, 50,2988, 134, 806,1869, 734,2036,3454, 180, # 144 + 995,1607, 156, 537,2907, 688,5018, 319,1305, 779,2145, 514,2379, 298,4512, 359, # 160 +2502, 90,2716,1338, 663, 11, 906,1099,2553, 20,2441, 182, 532,1716,5019, 732, # 176 +1376,4204,1311,1420,3206, 25,2317,1056, 113, 399, 382,1950, 242,3455,2474, 529, # 192 +3276, 475,1447,3683,5020, 117, 21, 656, 810,1297,2300,2334,3557,5021, 126,4205, # 208 + 706, 456, 150, 613,4513, 71,1118,2037,4206, 145,3092, 85, 835, 486,2115,1246, # 224 +1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,5022,2128,2359, 347,3815, 221, # 240 +3558,3135,5023,1956,1153,4207, 83, 296,1199,3093, 192, 624, 93,5024, 822,1898, # 256 +2823,3136, 795,2065, 991,1554,1542,1592, 27, 43,2867, 859, 139,1456, 860,4514, # 272 + 437, 712,3974, 164,2397,3137, 695, 211,3037,2097, 195,3975,1608,3559,3560,3684, # 288 +3976, 234, 811,2989,2098,3977,2233,1441,3561,1615,2380, 668,2077,1638, 305, 228, # 304 +1664,4515, 467, 415,5025, 262,2099,1593, 239, 108, 300, 200,1033, 512,1247,2078, # 320 +5026,5027,2176,3207,3685,2682, 593, 845,1062,3277, 88,1723,2038,3978,1951, 212, # 336 + 266, 152, 149, 468,1899,4208,4516, 77, 187,5028,3038, 37, 5,2990,5029,3979, # 352 +5030,5031, 39,2524,4517,2908,3208,2079, 55, 148, 74,4518, 545, 483,1474,1029, # 368 +1665, 217,1870,1531,3138,1104,2655,4209, 24, 172,3562, 900,3980,3563,3564,4519, # 384 + 32,1408,2824,1312, 329, 487,2360,2251,2717, 784,2683, 4,3039,3351,1427,1789, # 400 + 188, 109, 499,5032,3686,1717,1790, 888,1217,3040,4520,5033,3565,5034,3352,1520, # 416 +3687,3981, 196,1034, 775,5035,5036, 929,1816, 249, 439, 38,5037,1063,5038, 794, # 432 +3982,1435,2301, 46, 178,3278,2066,5039,2381,5040, 214,1709,4521, 804, 35, 707, # 448 + 324,3688,1601,2554, 140, 459,4210,5041,5042,1365, 839, 272, 978,2262,2580,3456, # 464 +2129,1363,3689,1423, 697, 100,3094, 48, 70,1231, 495,3139,2196,5043,1294,5044, # 480 +2080, 462, 586,1042,3279, 853, 256, 988, 185,2382,3457,1698, 434,1084,5045,3458, # 496 + 314,2625,2788,4522,2335,2336, 569,2285, 637,1817,2525, 757,1162,1879,1616,3459, # 512 + 287,1577,2116, 768,4523,1671,2868,3566,2526,1321,3816, 909,2418,5046,4211, 933, # 528 +3817,4212,2053,2361,1222,4524, 765,2419,1322, 786,4525,5047,1920,1462,1677,2909, # 544 +1699,5048,4526,1424,2442,3140,3690,2600,3353,1775,1941,3460,3983,4213, 309,1369, # 560 +1130,2825, 364,2234,1653,1299,3984,3567,3985,3986,2656, 525,1085,3041, 902,2001, # 576 +1475, 964,4527, 421,1845,1415,1057,2286, 940,1364,3141, 376,4528,4529,1381, 7, # 592 +2527, 983,2383, 336,1710,2684,1846, 321,3461, 559,1131,3042,2752,1809,1132,1313, # 608 + 265,1481,1858,5049, 352,1203,2826,3280, 167,1089, 420,2827, 776, 792,1724,3568, # 624 +4214,2443,3281,5050,4215,5051, 446, 229, 333,2753, 901,3818,1200,1557,4530,2657, # 640 +1921, 395,2754,2685,3819,4216,1836, 125, 916,3209,2626,4531,5052,5053,3820,5054, # 656 +5055,5056,4532,3142,3691,1133,2555,1757,3462,1510,2318,1409,3569,5057,2146, 438, # 672 +2601,2910,2384,3354,1068, 958,3043, 461, 311,2869,2686,4217,1916,3210,4218,1979, # 688 + 383, 750,2755,2627,4219, 274, 539, 385,1278,1442,5058,1154,1965, 384, 561, 210, # 704 + 98,1295,2556,3570,5059,1711,2420,1482,3463,3987,2911,1257, 129,5060,3821, 642, # 720 + 523,2789,2790,2658,5061, 141,2235,1333, 68, 176, 441, 876, 907,4220, 603,2602, # 736 + 710, 171,3464, 404, 549, 18,3143,2398,1410,3692,1666,5062,3571,4533,2912,4534, # 752 +5063,2991, 368,5064, 146, 366, 99, 871,3693,1543, 748, 807,1586,1185, 22,2263, # 768 + 379,3822,3211,5065,3212, 505,1942,2628,1992,1382,2319,5066, 380,2362, 218, 702, # 784 +1818,1248,3465,3044,3572,3355,3282,5067,2992,3694, 930,3283,3823,5068, 59,5069, # 800 + 585, 601,4221, 497,3466,1112,1314,4535,1802,5070,1223,1472,2177,5071, 749,1837, # 816 + 690,1900,3824,1773,3988,1476, 429,1043,1791,2236,2117, 917,4222, 447,1086,1629, # 832 +5072, 556,5073,5074,2021,1654, 844,1090, 105, 550, 966,1758,2828,1008,1783, 686, # 848 +1095,5075,2287, 793,1602,5076,3573,2603,4536,4223,2948,2302,4537,3825, 980,2503, # 864 + 544, 353, 527,4538, 908,2687,2913,5077, 381,2629,1943,1348,5078,1341,1252, 560, # 880 +3095,5079,3467,2870,5080,2054, 973, 886,2081, 143,4539,5081,5082, 157,3989, 496, # 896 +4224, 57, 840, 540,2039,4540,4541,3468,2118,1445, 970,2264,1748,1966,2082,4225, # 912 +3144,1234,1776,3284,2829,3695, 773,1206,2130,1066,2040,1326,3990,1738,1725,4226, # 928 + 279,3145, 51,1544,2604, 423,1578,2131,2067, 173,4542,1880,5083,5084,1583, 264, # 944 + 610,3696,4543,2444, 280, 154,5085,5086,5087,1739, 338,1282,3096, 693,2871,1411, # 960 +1074,3826,2445,5088,4544,5089,5090,1240, 952,2399,5091,2914,1538,2688, 685,1483, # 976 +4227,2475,1436, 953,4228,2055,4545, 671,2400, 79,4229,2446,3285, 608, 567,2689, # 992 +3469,4230,4231,1691, 393,1261,1792,2401,5092,4546,5093,5094,5095,5096,1383,1672, # 1008 +3827,3213,1464, 522,1119, 661,1150, 216, 675,4547,3991,1432,3574, 609,4548,2690, # 1024 +2402,5097,5098,5099,4232,3045, 0,5100,2476, 315, 231,2447, 301,3356,4549,2385, # 1040 +5101, 233,4233,3697,1819,4550,4551,5102, 96,1777,1315,2083,5103, 257,5104,1810, # 1056 +3698,2718,1139,1820,4234,2022,1124,2164,2791,1778,2659,5105,3097, 363,1655,3214, # 1072 +5106,2993,5107,5108,5109,3992,1567,3993, 718, 103,3215, 849,1443, 341,3357,2949, # 1088 +1484,5110,1712, 127, 67, 339,4235,2403, 679,1412, 821,5111,5112, 834, 738, 351, # 1104 +2994,2147, 846, 235,1497,1881, 418,1993,3828,2719, 186,1100,2148,2756,3575,1545, # 1120 +1355,2950,2872,1377, 583,3994,4236,2581,2995,5113,1298,3699,1078,2557,3700,2363, # 1136 + 78,3829,3830, 267,1289,2100,2002,1594,4237, 348, 369,1274,2197,2178,1838,4552, # 1152 +1821,2830,3701,2757,2288,2003,4553,2951,2758, 144,3358, 882,4554,3995,2759,3470, # 1168 +4555,2915,5114,4238,1726, 320,5115,3996,3046, 788,2996,5116,2831,1774,1327,2873, # 1184 +3997,2832,5117,1306,4556,2004,1700,3831,3576,2364,2660, 787,2023, 506, 824,3702, # 1200 + 534, 323,4557,1044,3359,2024,1901, 946,3471,5118,1779,1500,1678,5119,1882,4558, # 1216 + 165, 243,4559,3703,2528, 123, 683,4239, 764,4560, 36,3998,1793, 589,2916, 816, # 1232 + 626,1667,3047,2237,1639,1555,1622,3832,3999,5120,4000,2874,1370,1228,1933, 891, # 1248 +2084,2917, 304,4240,5121, 292,2997,2720,3577, 691,2101,4241,1115,4561, 118, 662, # 1264 +5122, 611,1156, 854,2386,1316,2875, 2, 386, 515,2918,5123,5124,3286, 868,2238, # 1280 +1486, 855,2661, 785,2216,3048,5125,1040,3216,3578,5126,3146, 448,5127,1525,5128, # 1296 +2165,4562,5129,3833,5130,4242,2833,3579,3147, 503, 818,4001,3148,1568, 814, 676, # 1312 +1444, 306,1749,5131,3834,1416,1030, 197,1428, 805,2834,1501,4563,5132,5133,5134, # 1328 +1994,5135,4564,5136,5137,2198, 13,2792,3704,2998,3149,1229,1917,5138,3835,2132, # 1344 +5139,4243,4565,2404,3580,5140,2217,1511,1727,1120,5141,5142, 646,3836,2448, 307, # 1360 +5143,5144,1595,3217,5145,5146,5147,3705,1113,1356,4002,1465,2529,2530,5148, 519, # 1376 +5149, 128,2133, 92,2289,1980,5150,4003,1512, 342,3150,2199,5151,2793,2218,1981, # 1392 +3360,4244, 290,1656,1317, 789, 827,2365,5152,3837,4566, 562, 581,4004,5153, 401, # 1408 +4567,2252, 94,4568,5154,1399,2794,5155,1463,2025,4569,3218,1944,5156, 828,1105, # 1424 +4245,1262,1394,5157,4246, 605,4570,5158,1784,2876,5159,2835, 819,2102, 578,2200, # 1440 +2952,5160,1502, 436,3287,4247,3288,2836,4005,2919,3472,3473,5161,2721,2320,5162, # 1456 +5163,2337,2068, 23,4571, 193, 826,3838,2103, 699,1630,4248,3098, 390,1794,1064, # 1472 +3581,5164,1579,3099,3100,1400,5165,4249,1839,1640,2877,5166,4572,4573, 137,4250, # 1488 + 598,3101,1967, 780, 104, 974,2953,5167, 278, 899, 253, 402, 572, 504, 493,1339, # 1504 +5168,4006,1275,4574,2582,2558,5169,3706,3049,3102,2253, 565,1334,2722, 863, 41, # 1520 +5170,5171,4575,5172,1657,2338, 19, 463,2760,4251, 606,5173,2999,3289,1087,2085, # 1536 +1323,2662,3000,5174,1631,1623,1750,4252,2691,5175,2878, 791,2723,2663,2339, 232, # 1552 +2421,5176,3001,1498,5177,2664,2630, 755,1366,3707,3290,3151,2026,1609, 119,1918, # 1568 +3474, 862,1026,4253,5178,4007,3839,4576,4008,4577,2265,1952,2477,5179,1125, 817, # 1584 +4254,4255,4009,1513,1766,2041,1487,4256,3050,3291,2837,3840,3152,5180,5181,1507, # 1600 +5182,2692, 733, 40,1632,1106,2879, 345,4257, 841,2531, 230,4578,3002,1847,3292, # 1616 +3475,5183,1263, 986,3476,5184, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562, # 1632 +4010,4011,2954, 967,2761,2665,1349, 592,2134,1692,3361,3003,1995,4258,1679,4012, # 1648 +1902,2188,5185, 739,3708,2724,1296,1290,5186,4259,2201,2202,1922,1563,2605,2559, # 1664 +1871,2762,3004,5187, 435,5188, 343,1108, 596, 17,1751,4579,2239,3477,3709,5189, # 1680 +4580, 294,3582,2955,1693, 477, 979, 281,2042,3583, 643,2043,3710,2631,2795,2266, # 1696 +1031,2340,2135,2303,3584,4581, 367,1249,2560,5190,3585,5191,4582,1283,3362,2005, # 1712 + 240,1762,3363,4583,4584, 836,1069,3153, 474,5192,2149,2532, 268,3586,5193,3219, # 1728 +1521,1284,5194,1658,1546,4260,5195,3587,3588,5196,4261,3364,2693,1685,4262, 961, # 1744 +1673,2632, 190,2006,2203,3841,4585,4586,5197, 570,2504,3711,1490,5198,4587,2633, # 1760 +3293,1957,4588, 584,1514, 396,1045,1945,5199,4589,1968,2449,5200,5201,4590,4013, # 1776 + 619,5202,3154,3294, 215,2007,2796,2561,3220,4591,3221,4592, 763,4263,3842,4593, # 1792 +5203,5204,1958,1767,2956,3365,3712,1174, 452,1477,4594,3366,3155,5205,2838,1253, # 1808 +2387,2189,1091,2290,4264, 492,5206, 638,1169,1825,2136,1752,4014, 648, 926,1021, # 1824 +1324,4595, 520,4596, 997, 847,1007, 892,4597,3843,2267,1872,3713,2405,1785,4598, # 1840 +1953,2957,3103,3222,1728,4265,2044,3714,4599,2008,1701,3156,1551, 30,2268,4266, # 1856 +5207,2027,4600,3589,5208, 501,5209,4267, 594,3478,2166,1822,3590,3479,3591,3223, # 1872 + 829,2839,4268,5210,1680,3157,1225,4269,5211,3295,4601,4270,3158,2341,5212,4602, # 1888 +4271,5213,4015,4016,5214,1848,2388,2606,3367,5215,4603, 374,4017, 652,4272,4273, # 1904 + 375,1140, 798,5216,5217,5218,2366,4604,2269, 546,1659, 138,3051,2450,4605,5219, # 1920 +2254, 612,1849, 910, 796,3844,1740,1371, 825,3845,3846,5220,2920,2562,5221, 692, # 1936 + 444,3052,2634, 801,4606,4274,5222,1491, 244,1053,3053,4275,4276, 340,5223,4018, # 1952 +1041,3005, 293,1168, 87,1357,5224,1539, 959,5225,2240, 721, 694,4277,3847, 219, # 1968 +1478, 644,1417,3368,2666,1413,1401,1335,1389,4019,5226,5227,3006,2367,3159,1826, # 1984 + 730,1515, 184,2840, 66,4607,5228,1660,2958, 246,3369, 378,1457, 226,3480, 975, # 2000 +4020,2959,1264,3592, 674, 696,5229, 163,5230,1141,2422,2167, 713,3593,3370,4608, # 2016 +4021,5231,5232,1186, 15,5233,1079,1070,5234,1522,3224,3594, 276,1050,2725, 758, # 2032 +1126, 653,2960,3296,5235,2342, 889,3595,4022,3104,3007, 903,1250,4609,4023,3481, # 2048 +3596,1342,1681,1718, 766,3297, 286, 89,2961,3715,5236,1713,5237,2607,3371,3008, # 2064 +5238,2962,2219,3225,2880,5239,4610,2505,2533, 181, 387,1075,4024, 731,2190,3372, # 2080 +5240,3298, 310, 313,3482,2304, 770,4278, 54,3054, 189,4611,3105,3848,4025,5241, # 2096 +1230,1617,1850, 355,3597,4279,4612,3373, 111,4280,3716,1350,3160,3483,3055,4281, # 2112 +2150,3299,3598,5242,2797,4026,4027,3009, 722,2009,5243,1071, 247,1207,2343,2478, # 2128 +1378,4613,2010, 864,1437,1214,4614, 373,3849,1142,2220, 667,4615, 442,2763,2563, # 2144 +3850,4028,1969,4282,3300,1840, 837, 170,1107, 934,1336,1883,5244,5245,2119,4283, # 2160 +2841, 743,1569,5246,4616,4284, 582,2389,1418,3484,5247,1803,5248, 357,1395,1729, # 2176 +3717,3301,2423,1564,2241,5249,3106,3851,1633,4617,1114,2086,4285,1532,5250, 482, # 2192 +2451,4618,5251,5252,1492, 833,1466,5253,2726,3599,1641,2842,5254,1526,1272,3718, # 2208 +4286,1686,1795, 416,2564,1903,1954,1804,5255,3852,2798,3853,1159,2321,5256,2881, # 2224 +4619,1610,1584,3056,2424,2764, 443,3302,1163,3161,5257,5258,4029,5259,4287,2506, # 2240 +3057,4620,4030,3162,2104,1647,3600,2011,1873,4288,5260,4289, 431,3485,5261, 250, # 2256 + 97, 81,4290,5262,1648,1851,1558, 160, 848,5263, 866, 740,1694,5264,2204,2843, # 2272 +3226,4291,4621,3719,1687, 950,2479, 426, 469,3227,3720,3721,4031,5265,5266,1188, # 2288 + 424,1996, 861,3601,4292,3854,2205,2694, 168,1235,3602,4293,5267,2087,1674,4622, # 2304 +3374,3303, 220,2565,1009,5268,3855, 670,3010, 332,1208, 717,5269,5270,3603,2452, # 2320 +4032,3375,5271, 513,5272,1209,2882,3376,3163,4623,1080,5273,5274,5275,5276,2534, # 2336 +3722,3604, 815,1587,4033,4034,5277,3605,3486,3856,1254,4624,1328,3058,1390,4035, # 2352 +1741,4036,3857,4037,5278, 236,3858,2453,3304,5279,5280,3723,3859,1273,3860,4625, # 2368 +5281, 308,5282,4626, 245,4627,1852,2480,1307,2583, 430, 715,2137,2454,5283, 270, # 2384 + 199,2883,4038,5284,3606,2727,1753, 761,1754, 725,1661,1841,4628,3487,3724,5285, # 2400 +5286, 587, 14,3305, 227,2608, 326, 480,2270, 943,2765,3607, 291, 650,1884,5287, # 2416 +1702,1226, 102,1547, 62,3488, 904,4629,3489,1164,4294,5288,5289,1224,1548,2766, # 2432 + 391, 498,1493,5290,1386,1419,5291,2056,1177,4630, 813, 880,1081,2368, 566,1145, # 2448 +4631,2291,1001,1035,2566,2609,2242, 394,1286,5292,5293,2069,5294, 86,1494,1730, # 2464 +4039, 491,1588, 745, 897,2963, 843,3377,4040,2767,2884,3306,1768, 998,2221,2070, # 2480 + 397,1827,1195,1970,3725,3011,3378, 284,5295,3861,2507,2138,2120,1904,5296,4041, # 2496 +2151,4042,4295,1036,3490,1905, 114,2567,4296, 209,1527,5297,5298,2964,2844,2635, # 2512 +2390,2728,3164, 812,2568,5299,3307,5300,1559, 737,1885,3726,1210, 885, 28,2695, # 2528 +3608,3862,5301,4297,1004,1780,4632,5302, 346,1982,2222,2696,4633,3863,1742, 797, # 2544 +1642,4043,1934,1072,1384,2152, 896,4044,3308,3727,3228,2885,3609,5303,2569,1959, # 2560 +4634,2455,1786,5304,5305,5306,4045,4298,1005,1308,3728,4299,2729,4635,4636,1528, # 2576 +2610, 161,1178,4300,1983, 987,4637,1101,4301, 631,4046,1157,3229,2425,1343,1241, # 2592 +1016,2243,2570, 372, 877,2344,2508,1160, 555,1935, 911,4047,5307, 466,1170, 169, # 2608 +1051,2921,2697,3729,2481,3012,1182,2012,2571,1251,2636,5308, 992,2345,3491,1540, # 2624 +2730,1201,2071,2406,1997,2482,5309,4638, 528,1923,2191,1503,1874,1570,2369,3379, # 2640 +3309,5310, 557,1073,5311,1828,3492,2088,2271,3165,3059,3107, 767,3108,2799,4639, # 2656 +1006,4302,4640,2346,1267,2179,3730,3230, 778,4048,3231,2731,1597,2667,5312,4641, # 2672 +5313,3493,5314,5315,5316,3310,2698,1433,3311, 131, 95,1504,4049, 723,4303,3166, # 2688 +1842,3610,2768,2192,4050,2028,2105,3731,5317,3013,4051,1218,5318,3380,3232,4052, # 2704 +4304,2584, 248,1634,3864, 912,5319,2845,3732,3060,3865, 654, 53,5320,3014,5321, # 2720 +1688,4642, 777,3494,1032,4053,1425,5322, 191, 820,2121,2846, 971,4643, 931,3233, # 2736 + 135, 664, 783,3866,1998, 772,2922,1936,4054,3867,4644,2923,3234, 282,2732, 640, # 2752 +1372,3495,1127, 922, 325,3381,5323,5324, 711,2045,5325,5326,4055,2223,2800,1937, # 2768 +4056,3382,2224,2255,3868,2305,5327,4645,3869,1258,3312,4057,3235,2139,2965,4058, # 2784 +4059,5328,2225, 258,3236,4646, 101,1227,5329,3313,1755,5330,1391,3314,5331,2924, # 2800 +2057, 893,5332,5333,5334,1402,4305,2347,5335,5336,3237,3611,5337,5338, 878,1325, # 2816 +1781,2801,4647, 259,1385,2585, 744,1183,2272,4648,5339,4060,2509,5340, 684,1024, # 2832 +4306,5341, 472,3612,3496,1165,3315,4061,4062, 322,2153, 881, 455,1695,1152,1340, # 2848 + 660, 554,2154,4649,1058,4650,4307, 830,1065,3383,4063,4651,1924,5342,1703,1919, # 2864 +5343, 932,2273, 122,5344,4652, 947, 677,5345,3870,2637, 297,1906,1925,2274,4653, # 2880 +2322,3316,5346,5347,4308,5348,4309, 84,4310, 112, 989,5349, 547,1059,4064, 701, # 2896 +3613,1019,5350,4311,5351,3497, 942, 639, 457,2306,2456, 993,2966, 407, 851, 494, # 2912 +4654,3384, 927,5352,1237,5353,2426,3385, 573,4312, 680, 921,2925,1279,1875, 285, # 2928 + 790,1448,1984, 719,2168,5354,5355,4655,4065,4066,1649,5356,1541, 563,5357,1077, # 2944 +5358,3386,3061,3498, 511,3015,4067,4068,3733,4069,1268,2572,3387,3238,4656,4657, # 2960 +5359, 535,1048,1276,1189,2926,2029,3167,1438,1373,2847,2967,1134,2013,5360,4313, # 2976 +1238,2586,3109,1259,5361, 700,5362,2968,3168,3734,4314,5363,4315,1146,1876,1907, # 2992 +4658,2611,4070, 781,2427, 132,1589, 203, 147, 273,2802,2407, 898,1787,2155,4071, # 3008 +4072,5364,3871,2803,5365,5366,4659,4660,5367,3239,5368,1635,3872, 965,5369,1805, # 3024 +2699,1516,3614,1121,1082,1329,3317,4073,1449,3873, 65,1128,2848,2927,2769,1590, # 3040 +3874,5370,5371, 12,2668, 45, 976,2587,3169,4661, 517,2535,1013,1037,3240,5372, # 3056 +3875,2849,5373,3876,5374,3499,5375,2612, 614,1999,2323,3877,3110,2733,2638,5376, # 3072 +2588,4316, 599,1269,5377,1811,3735,5378,2700,3111, 759,1060, 489,1806,3388,3318, # 3088 +1358,5379,5380,2391,1387,1215,2639,2256, 490,5381,5382,4317,1759,2392,2348,5383, # 3104 +4662,3878,1908,4074,2640,1807,3241,4663,3500,3319,2770,2349, 874,5384,5385,3501, # 3120 +3736,1859, 91,2928,3737,3062,3879,4664,5386,3170,4075,2669,5387,3502,1202,1403, # 3136 +3880,2969,2536,1517,2510,4665,3503,2511,5388,4666,5389,2701,1886,1495,1731,4076, # 3152 +2370,4667,5390,2030,5391,5392,4077,2702,1216, 237,2589,4318,2324,4078,3881,4668, # 3168 +4669,2703,3615,3504, 445,4670,5393,5394,5395,5396,2771, 61,4079,3738,1823,4080, # 3184 +5397, 687,2046, 935, 925, 405,2670, 703,1096,1860,2734,4671,4081,1877,1367,2704, # 3200 +3389, 918,2106,1782,2483, 334,3320,1611,1093,4672, 564,3171,3505,3739,3390, 945, # 3216 +2641,2058,4673,5398,1926, 872,4319,5399,3506,2705,3112, 349,4320,3740,4082,4674, # 3232 +3882,4321,3741,2156,4083,4675,4676,4322,4677,2408,2047, 782,4084, 400, 251,4323, # 3248 +1624,5400,5401, 277,3742, 299,1265, 476,1191,3883,2122,4324,4325,1109, 205,5402, # 3264 +2590,1000,2157,3616,1861,5403,5404,5405,4678,5406,4679,2573, 107,2484,2158,4085, # 3280 +3507,3172,5407,1533, 541,1301, 158, 753,4326,2886,3617,5408,1696, 370,1088,4327, # 3296 +4680,3618, 579, 327, 440, 162,2244, 269,1938,1374,3508, 968,3063, 56,1396,3113, # 3312 +2107,3321,3391,5409,1927,2159,4681,3016,5410,3619,5411,5412,3743,4682,2485,5413, # 3328 +2804,5414,1650,4683,5415,2613,5416,5417,4086,2671,3392,1149,3393,4087,3884,4088, # 3344 +5418,1076, 49,5419, 951,3242,3322,3323, 450,2850, 920,5420,1812,2805,2371,4328, # 3360 +1909,1138,2372,3885,3509,5421,3243,4684,1910,1147,1518,2428,4685,3886,5422,4686, # 3376 +2393,2614, 260,1796,3244,5423,5424,3887,3324, 708,5425,3620,1704,5426,3621,1351, # 3392 +1618,3394,3017,1887, 944,4329,3395,4330,3064,3396,4331,5427,3744, 422, 413,1714, # 3408 +3325, 500,2059,2350,4332,2486,5428,1344,1911, 954,5429,1668,5430,5431,4089,2409, # 3424 +4333,3622,3888,4334,5432,2307,1318,2512,3114, 133,3115,2887,4687, 629, 31,2851, # 3440 +2706,3889,4688, 850, 949,4689,4090,2970,1732,2089,4335,1496,1853,5433,4091, 620, # 3456 +3245, 981,1242,3745,3397,1619,3746,1643,3326,2140,2457,1971,1719,3510,2169,5434, # 3472 +3246,5435,5436,3398,1829,5437,1277,4690,1565,2048,5438,1636,3623,3116,5439, 869, # 3488 +2852, 655,3890,3891,3117,4092,3018,3892,1310,3624,4691,5440,5441,5442,1733, 558, # 3504 +4692,3747, 335,1549,3065,1756,4336,3748,1946,3511,1830,1291,1192, 470,2735,2108, # 3520 +2806, 913,1054,4093,5443,1027,5444,3066,4094,4693, 982,2672,3399,3173,3512,3247, # 3536 +3248,1947,2807,5445, 571,4694,5446,1831,5447,3625,2591,1523,2429,5448,2090, 984, # 3552 +4695,3749,1960,5449,3750, 852, 923,2808,3513,3751, 969,1519, 999,2049,2325,1705, # 3568 +5450,3118, 615,1662, 151, 597,4095,2410,2326,1049, 275,4696,3752,4337, 568,3753, # 3584 +3626,2487,4338,3754,5451,2430,2275, 409,3249,5452,1566,2888,3514,1002, 769,2853, # 3600 + 194,2091,3174,3755,2226,3327,4339, 628,1505,5453,5454,1763,2180,3019,4096, 521, # 3616 +1161,2592,1788,2206,2411,4697,4097,1625,4340,4341, 412, 42,3119, 464,5455,2642, # 3632 +4698,3400,1760,1571,2889,3515,2537,1219,2207,3893,2643,2141,2373,4699,4700,3328, # 3648 +1651,3401,3627,5456,5457,3628,2488,3516,5458,3756,5459,5460,2276,2092, 460,5461, # 3664 +4701,5462,3020, 962, 588,3629, 289,3250,2644,1116, 52,5463,3067,1797,5464,5465, # 3680 +5466,1467,5467,1598,1143,3757,4342,1985,1734,1067,4702,1280,3402, 465,4703,1572, # 3696 + 510,5468,1928,2245,1813,1644,3630,5469,4704,3758,5470,5471,2673,1573,1534,5472, # 3712 +5473, 536,1808,1761,3517,3894,3175,2645,5474,5475,5476,4705,3518,2929,1912,2809, # 3728 +5477,3329,1122, 377,3251,5478, 360,5479,5480,4343,1529, 551,5481,2060,3759,1769, # 3744 +2431,5482,2930,4344,3330,3120,2327,2109,2031,4706,1404, 136,1468,1479, 672,1171, # 3760 +3252,2308, 271,3176,5483,2772,5484,2050, 678,2736, 865,1948,4707,5485,2014,4098, # 3776 +2971,5486,2737,2227,1397,3068,3760,4708,4709,1735,2931,3403,3631,5487,3895, 509, # 3792 +2854,2458,2890,3896,5488,5489,3177,3178,4710,4345,2538,4711,2309,1166,1010, 552, # 3808 + 681,1888,5490,5491,2972,2973,4099,1287,1596,1862,3179, 358, 453, 736, 175, 478, # 3824 +1117, 905,1167,1097,5492,1854,1530,5493,1706,5494,2181,3519,2292,3761,3520,3632, # 3840 +4346,2093,4347,5495,3404,1193,2489,4348,1458,2193,2208,1863,1889,1421,3331,2932, # 3856 +3069,2182,3521, 595,2123,5496,4100,5497,5498,4349,1707,2646, 223,3762,1359, 751, # 3872 +3121, 183,3522,5499,2810,3021, 419,2374, 633, 704,3897,2394, 241,5500,5501,5502, # 3888 + 838,3022,3763,2277,2773,2459,3898,1939,2051,4101,1309,3122,2246,1181,5503,1136, # 3904 +2209,3899,2375,1446,4350,2310,4712,5504,5505,4351,1055,2615, 484,3764,5506,4102, # 3920 + 625,4352,2278,3405,1499,4353,4103,5507,4104,4354,3253,2279,2280,3523,5508,5509, # 3936 +2774, 808,2616,3765,3406,4105,4355,3123,2539, 526,3407,3900,4356, 955,5510,1620, # 3952 +4357,2647,2432,5511,1429,3766,1669,1832, 994, 928,5512,3633,1260,5513,5514,5515, # 3968 +1949,2293, 741,2933,1626,4358,2738,2460, 867,1184, 362,3408,1392,5516,5517,4106, # 3984 +4359,1770,1736,3254,2934,4713,4714,1929,2707,1459,1158,5518,3070,3409,2891,1292, # 4000 +1930,2513,2855,3767,1986,1187,2072,2015,2617,4360,5519,2574,2514,2170,3768,2490, # 4016 +3332,5520,3769,4715,5521,5522, 666,1003,3023,1022,3634,4361,5523,4716,1814,2257, # 4032 + 574,3901,1603, 295,1535, 705,3902,4362, 283, 858, 417,5524,5525,3255,4717,4718, # 4048 +3071,1220,1890,1046,2281,2461,4107,1393,1599, 689,2575, 388,4363,5526,2491, 802, # 4064 +5527,2811,3903,2061,1405,2258,5528,4719,3904,2110,1052,1345,3256,1585,5529, 809, # 4080 +5530,5531,5532, 575,2739,3524, 956,1552,1469,1144,2328,5533,2329,1560,2462,3635, # 4096 +3257,4108, 616,2210,4364,3180,2183,2294,5534,1833,5535,3525,4720,5536,1319,3770, # 4112 +3771,1211,3636,1023,3258,1293,2812,5537,5538,5539,3905, 607,2311,3906, 762,2892, # 4128 +1439,4365,1360,4721,1485,3072,5540,4722,1038,4366,1450,2062,2648,4367,1379,4723, # 4144 +2593,5541,5542,4368,1352,1414,2330,2935,1172,5543,5544,3907,3908,4724,1798,1451, # 4160 +5545,5546,5547,5548,2936,4109,4110,2492,2351, 411,4111,4112,3637,3333,3124,4725, # 4176 +1561,2674,1452,4113,1375,5549,5550, 47,2974, 316,5551,1406,1591,2937,3181,5552, # 4192 +1025,2142,3125,3182, 354,2740, 884,2228,4369,2412, 508,3772, 726,3638, 996,2433, # 4208 +3639, 729,5553, 392,2194,1453,4114,4726,3773,5554,5555,2463,3640,2618,1675,2813, # 4224 + 919,2352,2975,2353,1270,4727,4115, 73,5556,5557, 647,5558,3259,2856,2259,1550, # 4240 +1346,3024,5559,1332, 883,3526,5560,5561,5562,5563,3334,2775,5564,1212, 831,1347, # 4256 +4370,4728,2331,3909,1864,3073, 720,3910,4729,4730,3911,5565,4371,5566,5567,4731, # 4272 +5568,5569,1799,4732,3774,2619,4733,3641,1645,2376,4734,5570,2938, 669,2211,2675, # 4288 +2434,5571,2893,5572,5573,1028,3260,5574,4372,2413,5575,2260,1353,5576,5577,4735, # 4304 +3183, 518,5578,4116,5579,4373,1961,5580,2143,4374,5581,5582,3025,2354,2355,3912, # 4320 + 516,1834,1454,4117,2708,4375,4736,2229,2620,1972,1129,3642,5583,2776,5584,2976, # 4336 +1422, 577,1470,3026,1524,3410,5585,5586, 432,4376,3074,3527,5587,2594,1455,2515, # 4352 +2230,1973,1175,5588,1020,2741,4118,3528,4737,5589,2742,5590,1743,1361,3075,3529, # 4368 +2649,4119,4377,4738,2295, 895, 924,4378,2171, 331,2247,3076, 166,1627,3077,1098, # 4384 +5591,1232,2894,2231,3411,4739, 657, 403,1196,2377, 542,3775,3412,1600,4379,3530, # 4400 +5592,4740,2777,3261, 576, 530,1362,4741,4742,2540,2676,3776,4120,5593, 842,3913, # 4416 +5594,2814,2032,1014,4121, 213,2709,3413, 665, 621,4380,5595,3777,2939,2435,5596, # 4432 +2436,3335,3643,3414,4743,4381,2541,4382,4744,3644,1682,4383,3531,1380,5597, 724, # 4448 +2282, 600,1670,5598,1337,1233,4745,3126,2248,5599,1621,4746,5600, 651,4384,5601, # 4464 +1612,4385,2621,5602,2857,5603,2743,2312,3078,5604, 716,2464,3079, 174,1255,2710, # 4480 +4122,3645, 548,1320,1398, 728,4123,1574,5605,1891,1197,3080,4124,5606,3081,3082, # 4496 +3778,3646,3779, 747,5607, 635,4386,4747,5608,5609,5610,4387,5611,5612,4748,5613, # 4512 +3415,4749,2437, 451,5614,3780,2542,2073,4388,2744,4389,4125,5615,1764,4750,5616, # 4528 +4390, 350,4751,2283,2395,2493,5617,4391,4126,2249,1434,4127, 488,4752, 458,4392, # 4544 +4128,3781, 771,1330,2396,3914,2576,3184,2160,2414,1553,2677,3185,4393,5618,2494, # 4560 +2895,2622,1720,2711,4394,3416,4753,5619,2543,4395,5620,3262,4396,2778,5621,2016, # 4576 +2745,5622,1155,1017,3782,3915,5623,3336,2313, 201,1865,4397,1430,5624,4129,5625, # 4592 +5626,5627,5628,5629,4398,1604,5630, 414,1866, 371,2595,4754,4755,3532,2017,3127, # 4608 +4756,1708, 960,4399, 887, 389,2172,1536,1663,1721,5631,2232,4130,2356,2940,1580, # 4624 +5632,5633,1744,4757,2544,4758,4759,5634,4760,5635,2074,5636,4761,3647,3417,2896, # 4640 +4400,5637,4401,2650,3418,2815, 673,2712,2465, 709,3533,4131,3648,4402,5638,1148, # 4656 + 502, 634,5639,5640,1204,4762,3649,1575,4763,2623,3783,5641,3784,3128, 948,3263, # 4672 + 121,1745,3916,1110,5642,4403,3083,2516,3027,4132,3785,1151,1771,3917,1488,4133, # 4688 +1987,5643,2438,3534,5644,5645,2094,5646,4404,3918,1213,1407,2816, 531,2746,2545, # 4704 +3264,1011,1537,4764,2779,4405,3129,1061,5647,3786,3787,1867,2897,5648,2018, 120, # 4720 +4406,4407,2063,3650,3265,2314,3919,2678,3419,1955,4765,4134,5649,3535,1047,2713, # 4736 +1266,5650,1368,4766,2858, 649,3420,3920,2546,2747,1102,2859,2679,5651,5652,2000, # 4752 +5653,1111,3651,2977,5654,2495,3921,3652,2817,1855,3421,3788,5655,5656,3422,2415, # 4768 +2898,3337,3266,3653,5657,2577,5658,3654,2818,4135,1460, 856,5659,3655,5660,2899, # 4784 +2978,5661,2900,3922,5662,4408, 632,2517, 875,3923,1697,3924,2296,5663,5664,4767, # 4800 +3028,1239, 580,4768,4409,5665, 914, 936,2075,1190,4136,1039,2124,5666,5667,5668, # 4816 +5669,3423,1473,5670,1354,4410,3925,4769,2173,3084,4137, 915,3338,4411,4412,3339, # 4832 +1605,1835,5671,2748, 398,3656,4413,3926,4138, 328,1913,2860,4139,3927,1331,4414, # 4848 +3029, 937,4415,5672,3657,4140,4141,3424,2161,4770,3425, 524, 742, 538,3085,1012, # 4864 +5673,5674,3928,2466,5675, 658,1103, 225,3929,5676,5677,4771,5678,4772,5679,3267, # 4880 +1243,5680,4142, 963,2250,4773,5681,2714,3658,3186,5682,5683,2596,2332,5684,4774, # 4896 +5685,5686,5687,3536, 957,3426,2547,2033,1931,2941,2467, 870,2019,3659,1746,2780, # 4912 +2781,2439,2468,5688,3930,5689,3789,3130,3790,3537,3427,3791,5690,1179,3086,5691, # 4928 +3187,2378,4416,3792,2548,3188,3131,2749,4143,5692,3428,1556,2549,2297, 977,2901, # 4944 +2034,4144,1205,3429,5693,1765,3430,3189,2125,1271, 714,1689,4775,3538,5694,2333, # 4960 +3931, 533,4417,3660,2184, 617,5695,2469,3340,3539,2315,5696,5697,3190,5698,5699, # 4976 +3932,1988, 618, 427,2651,3540,3431,5700,5701,1244,1690,5702,2819,4418,4776,5703, # 4992 +3541,4777,5704,2284,1576, 473,3661,4419,3432, 972,5705,3662,5706,3087,5707,5708, # 5008 +4778,4779,5709,3793,4145,4146,5710, 153,4780, 356,5711,1892,2902,4420,2144, 408, # 5024 + 803,2357,5712,3933,5713,4421,1646,2578,2518,4781,4782,3934,5714,3935,4422,5715, # 5040 +2416,3433, 752,5716,5717,1962,3341,2979,5718, 746,3030,2470,4783,4423,3794, 698, # 5056 +4784,1893,4424,3663,2550,4785,3664,3936,5719,3191,3434,5720,1824,1302,4147,2715, # 5072 +3937,1974,4425,5721,4426,3192, 823,1303,1288,1236,2861,3542,4148,3435, 774,3938, # 5088 +5722,1581,4786,1304,2862,3939,4787,5723,2440,2162,1083,3268,4427,4149,4428, 344, # 5104 +1173, 288,2316, 454,1683,5724,5725,1461,4788,4150,2597,5726,5727,4789, 985, 894, # 5120 +5728,3436,3193,5729,1914,2942,3795,1989,5730,2111,1975,5731,4151,5732,2579,1194, # 5136 + 425,5733,4790,3194,1245,3796,4429,5734,5735,2863,5736, 636,4791,1856,3940, 760, # 5152 +1800,5737,4430,2212,1508,4792,4152,1894,1684,2298,5738,5739,4793,4431,4432,2213, # 5168 + 479,5740,5741, 832,5742,4153,2496,5743,2980,2497,3797, 990,3132, 627,1815,2652, # 5184 +4433,1582,4434,2126,2112,3543,4794,5744, 799,4435,3195,5745,4795,2113,1737,3031, # 5200 +1018, 543, 754,4436,3342,1676,4796,4797,4154,4798,1489,5746,3544,5747,2624,2903, # 5216 +4155,5748,5749,2981,5750,5751,5752,5753,3196,4799,4800,2185,1722,5754,3269,3270, # 5232 +1843,3665,1715, 481, 365,1976,1857,5755,5756,1963,2498,4801,5757,2127,3666,3271, # 5248 + 433,1895,2064,2076,5758, 602,2750,5759,5760,5761,5762,5763,3032,1628,3437,5764, # 5264 +3197,4802,4156,2904,4803,2519,5765,2551,2782,5766,5767,5768,3343,4804,2905,5769, # 5280 +4805,5770,2864,4806,4807,1221,2982,4157,2520,5771,5772,5773,1868,1990,5774,5775, # 5296 +5776,1896,5777,5778,4808,1897,4158, 318,5779,2095,4159,4437,5780,5781, 485,5782, # 5312 + 938,3941, 553,2680, 116,5783,3942,3667,5784,3545,2681,2783,3438,3344,2820,5785, # 5328 +3668,2943,4160,1747,2944,2983,5786,5787, 207,5788,4809,5789,4810,2521,5790,3033, # 5344 + 890,3669,3943,5791,1878,3798,3439,5792,2186,2358,3440,1652,5793,5794,5795, 941, # 5360 +2299, 208,3546,4161,2020, 330,4438,3944,2906,2499,3799,4439,4811,5796,5797,5798, # 5376 +) + diff --git a/robot/lib/python3.8/site-packages/chardet/big5prober.py b/robot/lib/python3.8/site-packages/chardet/big5prober.py new file mode 100644 index 0000000000000000000000000000000000000000..98f9970122088c14a5830e091ca8a12fc8e4c563 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/big5prober.py @@ -0,0 +1,47 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import Big5DistributionAnalysis +from .mbcssm import BIG5_SM_MODEL + + +class Big5Prober(MultiByteCharSetProber): + def __init__(self): + super(Big5Prober, self).__init__() + self.coding_sm = CodingStateMachine(BIG5_SM_MODEL) + self.distribution_analyzer = Big5DistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "Big5" + + @property + def language(self): + return "Chinese" diff --git a/robot/lib/python3.8/site-packages/chardet/chardistribution.py b/robot/lib/python3.8/site-packages/chardet/chardistribution.py new file mode 100644 index 0000000000000000000000000000000000000000..c0395f4a45aaa5c4ba1824a81d8ef8f69b46dc60 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/chardistribution.py @@ -0,0 +1,233 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .euctwfreq import (EUCTW_CHAR_TO_FREQ_ORDER, EUCTW_TABLE_SIZE, + EUCTW_TYPICAL_DISTRIBUTION_RATIO) +from .euckrfreq import (EUCKR_CHAR_TO_FREQ_ORDER, EUCKR_TABLE_SIZE, + EUCKR_TYPICAL_DISTRIBUTION_RATIO) +from .gb2312freq import (GB2312_CHAR_TO_FREQ_ORDER, GB2312_TABLE_SIZE, + GB2312_TYPICAL_DISTRIBUTION_RATIO) +from .big5freq import (BIG5_CHAR_TO_FREQ_ORDER, BIG5_TABLE_SIZE, + BIG5_TYPICAL_DISTRIBUTION_RATIO) +from .jisfreq import (JIS_CHAR_TO_FREQ_ORDER, JIS_TABLE_SIZE, + JIS_TYPICAL_DISTRIBUTION_RATIO) + + +class CharDistributionAnalysis(object): + ENOUGH_DATA_THRESHOLD = 1024 + SURE_YES = 0.99 + SURE_NO = 0.01 + MINIMUM_DATA_THRESHOLD = 3 + + def __init__(self): + # Mapping table to get frequency order from char order (get from + # GetOrder()) + self._char_to_freq_order = None + self._table_size = None # Size of above table + # This is a constant value which varies from language to language, + # used in calculating confidence. See + # http://www.mozilla.org/projects/intl/UniversalCharsetDetection.html + # for further detail. + self.typical_distribution_ratio = None + self._done = None + self._total_chars = None + self._freq_chars = None + self.reset() + + def reset(self): + """reset analyser, clear any state""" + # If this flag is set to True, detection is done and conclusion has + # been made + self._done = False + self._total_chars = 0 # Total characters encountered + # The number of characters whose frequency order is less than 512 + self._freq_chars = 0 + + def feed(self, char, char_len): + """feed a character with known length""" + if char_len == 2: + # we only care about 2-bytes character in our distribution analysis + order = self.get_order(char) + else: + order = -1 + if order >= 0: + self._total_chars += 1 + # order is valid + if order < self._table_size: + if 512 > self._char_to_freq_order[order]: + self._freq_chars += 1 + + def get_confidence(self): + """return confidence based on existing data""" + # if we didn't receive any character in our consideration range, + # return negative answer + if self._total_chars <= 0 or self._freq_chars <= self.MINIMUM_DATA_THRESHOLD: + return self.SURE_NO + + if self._total_chars != self._freq_chars: + r = (self._freq_chars / ((self._total_chars - self._freq_chars) + * self.typical_distribution_ratio)) + if r < self.SURE_YES: + return r + + # normalize confidence (we don't want to be 100% sure) + return self.SURE_YES + + def got_enough_data(self): + # It is not necessary to receive all data to draw conclusion. + # For charset detection, certain amount of data is enough + return self._total_chars > self.ENOUGH_DATA_THRESHOLD + + def get_order(self, byte_str): + # We do not handle characters based on the original encoding string, + # but convert this encoding string to a number, here called order. + # This allows multiple encodings of a language to share one frequency + # table. + return -1 + + +class EUCTWDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(EUCTWDistributionAnalysis, self).__init__() + self._char_to_freq_order = EUCTW_CHAR_TO_FREQ_ORDER + self._table_size = EUCTW_TABLE_SIZE + self.typical_distribution_ratio = EUCTW_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str): + # for euc-TW encoding, we are interested + # first byte range: 0xc4 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char = byte_str[0] + if first_char >= 0xC4: + return 94 * (first_char - 0xC4) + byte_str[1] - 0xA1 + else: + return -1 + + +class EUCKRDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(EUCKRDistributionAnalysis, self).__init__() + self._char_to_freq_order = EUCKR_CHAR_TO_FREQ_ORDER + self._table_size = EUCKR_TABLE_SIZE + self.typical_distribution_ratio = EUCKR_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str): + # for euc-KR encoding, we are interested + # first byte range: 0xb0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char = byte_str[0] + if first_char >= 0xB0: + return 94 * (first_char - 0xB0) + byte_str[1] - 0xA1 + else: + return -1 + + +class GB2312DistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(GB2312DistributionAnalysis, self).__init__() + self._char_to_freq_order = GB2312_CHAR_TO_FREQ_ORDER + self._table_size = GB2312_TABLE_SIZE + self.typical_distribution_ratio = GB2312_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str): + # for GB2312 encoding, we are interested + # first byte range: 0xb0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char, second_char = byte_str[0], byte_str[1] + if (first_char >= 0xB0) and (second_char >= 0xA1): + return 94 * (first_char - 0xB0) + second_char - 0xA1 + else: + return -1 + + +class Big5DistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(Big5DistributionAnalysis, self).__init__() + self._char_to_freq_order = BIG5_CHAR_TO_FREQ_ORDER + self._table_size = BIG5_TABLE_SIZE + self.typical_distribution_ratio = BIG5_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str): + # for big5 encoding, we are interested + # first byte range: 0xa4 -- 0xfe + # second byte range: 0x40 -- 0x7e , 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char, second_char = byte_str[0], byte_str[1] + if first_char >= 0xA4: + if second_char >= 0xA1: + return 157 * (first_char - 0xA4) + second_char - 0xA1 + 63 + else: + return 157 * (first_char - 0xA4) + second_char - 0x40 + else: + return -1 + + +class SJISDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(SJISDistributionAnalysis, self).__init__() + self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER + self._table_size = JIS_TABLE_SIZE + self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str): + # for sjis encoding, we are interested + # first byte range: 0x81 -- 0x9f , 0xe0 -- 0xfe + # second byte range: 0x40 -- 0x7e, 0x81 -- oxfe + # no validation needed here. State machine has done that + first_char, second_char = byte_str[0], byte_str[1] + if (first_char >= 0x81) and (first_char <= 0x9F): + order = 188 * (first_char - 0x81) + elif (first_char >= 0xE0) and (first_char <= 0xEF): + order = 188 * (first_char - 0xE0 + 31) + else: + return -1 + order = order + second_char - 0x40 + if second_char > 0x7F: + order = -1 + return order + + +class EUCJPDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(EUCJPDistributionAnalysis, self).__init__() + self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER + self._table_size = JIS_TABLE_SIZE + self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str): + # for euc-JP encoding, we are interested + # first byte range: 0xa0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + char = byte_str[0] + if char >= 0xA0: + return 94 * (char - 0xA1) + byte_str[1] - 0xa1 + else: + return -1 diff --git a/robot/lib/python3.8/site-packages/chardet/charsetgroupprober.py b/robot/lib/python3.8/site-packages/chardet/charsetgroupprober.py new file mode 100644 index 0000000000000000000000000000000000000000..8b3738efd8ea1e42d196d381d2809beae7f4c649 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/charsetgroupprober.py @@ -0,0 +1,106 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .enums import ProbingState +from .charsetprober import CharSetProber + + +class CharSetGroupProber(CharSetProber): + def __init__(self, lang_filter=None): + super(CharSetGroupProber, self).__init__(lang_filter=lang_filter) + self._active_num = 0 + self.probers = [] + self._best_guess_prober = None + + def reset(self): + super(CharSetGroupProber, self).reset() + self._active_num = 0 + for prober in self.probers: + if prober: + prober.reset() + prober.active = True + self._active_num += 1 + self._best_guess_prober = None + + @property + def charset_name(self): + if not self._best_guess_prober: + self.get_confidence() + if not self._best_guess_prober: + return None + return self._best_guess_prober.charset_name + + @property + def language(self): + if not self._best_guess_prober: + self.get_confidence() + if not self._best_guess_prober: + return None + return self._best_guess_prober.language + + def feed(self, byte_str): + for prober in self.probers: + if not prober: + continue + if not prober.active: + continue + state = prober.feed(byte_str) + if not state: + continue + if state == ProbingState.FOUND_IT: + self._best_guess_prober = prober + return self.state + elif state == ProbingState.NOT_ME: + prober.active = False + self._active_num -= 1 + if self._active_num <= 0: + self._state = ProbingState.NOT_ME + return self.state + return self.state + + def get_confidence(self): + state = self.state + if state == ProbingState.FOUND_IT: + return 0.99 + elif state == ProbingState.NOT_ME: + return 0.01 + best_conf = 0.0 + self._best_guess_prober = None + for prober in self.probers: + if not prober: + continue + if not prober.active: + self.logger.debug('%s not active', prober.charset_name) + continue + conf = prober.get_confidence() + self.logger.debug('%s %s confidence = %s', prober.charset_name, prober.language, conf) + if best_conf < conf: + best_conf = conf + self._best_guess_prober = prober + if not self._best_guess_prober: + return 0.0 + return best_conf diff --git a/robot/lib/python3.8/site-packages/chardet/charsetprober.py b/robot/lib/python3.8/site-packages/chardet/charsetprober.py new file mode 100644 index 0000000000000000000000000000000000000000..eac4e5986578636ad414648e6015e8b7e9f10432 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/charsetprober.py @@ -0,0 +1,145 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import logging +import re + +from .enums import ProbingState + + +class CharSetProber(object): + + SHORTCUT_THRESHOLD = 0.95 + + def __init__(self, lang_filter=None): + self._state = None + self.lang_filter = lang_filter + self.logger = logging.getLogger(__name__) + + def reset(self): + self._state = ProbingState.DETECTING + + @property + def charset_name(self): + return None + + def feed(self, buf): + pass + + @property + def state(self): + return self._state + + def get_confidence(self): + return 0.0 + + @staticmethod + def filter_high_byte_only(buf): + buf = re.sub(b'([\x00-\x7F])+', b' ', buf) + return buf + + @staticmethod + def filter_international_words(buf): + """ + We define three types of bytes: + alphabet: english alphabets [a-zA-Z] + international: international characters [\x80-\xFF] + marker: everything else [^a-zA-Z\x80-\xFF] + + The input buffer can be thought to contain a series of words delimited + by markers. This function works to filter all words that contain at + least one international character. All contiguous sequences of markers + are replaced by a single space ascii character. + + This filter applies to all scripts which do not use English characters. + """ + filtered = bytearray() + + # This regex expression filters out only words that have at-least one + # international character. The word may include one marker character at + # the end. + words = re.findall(b'[a-zA-Z]*[\x80-\xFF]+[a-zA-Z]*[^a-zA-Z\x80-\xFF]?', + buf) + + for word in words: + filtered.extend(word[:-1]) + + # If the last character in the word is a marker, replace it with a + # space as markers shouldn't affect our analysis (they are used + # similarly across all languages and may thus have similar + # frequencies). + last_char = word[-1:] + if not last_char.isalpha() and last_char < b'\x80': + last_char = b' ' + filtered.extend(last_char) + + return filtered + + @staticmethod + def filter_with_english_letters(buf): + """ + Returns a copy of ``buf`` that retains only the sequences of English + alphabet and high byte characters that are not between <> characters. + Also retains English alphabet and high byte characters immediately + before occurrences of >. + + This filter can be applied to all scripts which contain both English + characters and extended ASCII characters, but is currently only used by + ``Latin1Prober``. + """ + filtered = bytearray() + in_tag = False + prev = 0 + + for curr in range(len(buf)): + # Slice here to get bytes instead of an int with Python 3 + buf_char = buf[curr:curr + 1] + # Check if we're coming out of or entering an HTML tag + if buf_char == b'>': + in_tag = False + elif buf_char == b'<': + in_tag = True + + # If current character is not extended-ASCII and not alphabetic... + if buf_char < b'\x80' and not buf_char.isalpha(): + # ...and we're not in a tag + if curr > prev and not in_tag: + # Keep everything after last non-extended-ASCII, + # non-alphabetic character + filtered.extend(buf[prev:curr]) + # Output a space to delimit stretch we kept + filtered.extend(b' ') + prev = curr + 1 + + # If we're not in a tag... + if not in_tag: + # Keep everything after last non-extended-ASCII, non-alphabetic + # character + filtered.extend(buf[prev:]) + + return filtered diff --git a/robot/lib/python3.8/site-packages/chardet/cli/__init__.py b/robot/lib/python3.8/site-packages/chardet/cli/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/cli/__init__.py @@ -0,0 +1 @@ + diff --git a/robot/lib/python3.8/site-packages/chardet/cli/chardetect.py b/robot/lib/python3.8/site-packages/chardet/cli/chardetect.py new file mode 100644 index 0000000000000000000000000000000000000000..f0a4cc5d79f2b42486631f96558a47093dd6298e --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/cli/chardetect.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +""" +Script which takes one or more file paths and reports on their detected +encodings + +Example:: + + % chardetect somefile someotherfile + somefile: windows-1252 with confidence 0.5 + someotherfile: ascii with confidence 1.0 + +If no paths are provided, it takes its input from stdin. + +""" + +from __future__ import absolute_import, print_function, unicode_literals + +import argparse +import sys + +from chardet import __version__ +from chardet.compat import PY2 +from chardet.universaldetector import UniversalDetector + + +def description_of(lines, name='stdin'): + """ + Return a string describing the probable encoding of a file or + list of strings. + + :param lines: The lines to get the encoding of. + :type lines: Iterable of bytes + :param name: Name of file or collection of lines + :type name: str + """ + u = UniversalDetector() + for line in lines: + line = bytearray(line) + u.feed(line) + # shortcut out of the loop to save reading further - particularly useful if we read a BOM. + if u.done: + break + u.close() + result = u.result + if PY2: + name = name.decode(sys.getfilesystemencoding(), 'ignore') + if result['encoding']: + return '{0}: {1} with confidence {2}'.format(name, result['encoding'], + result['confidence']) + else: + return '{0}: no result'.format(name) + + +def main(argv=None): + """ + Handles command line arguments and gets things started. + + :param argv: List of arguments, as if specified on the command-line. + If None, ``sys.argv[1:]`` is used instead. + :type argv: list of str + """ + # Get command line arguments + parser = argparse.ArgumentParser( + description="Takes one or more file paths and reports their detected \ + encodings") + parser.add_argument('input', + help='File whose encoding we would like to determine. \ + (default: stdin)', + type=argparse.FileType('rb'), nargs='*', + default=[sys.stdin if PY2 else sys.stdin.buffer]) + parser.add_argument('--version', action='version', + version='%(prog)s {0}'.format(__version__)) + args = parser.parse_args(argv) + + for f in args.input: + if f.isatty(): + print("You are running chardetect interactively. Press " + + "CTRL-D twice at the start of a blank line to signal the " + + "end of your input. If you want help, run chardetect " + + "--help\n", file=sys.stderr) + print(description_of(f, f.name)) + + +if __name__ == '__main__': + main() diff --git a/robot/lib/python3.8/site-packages/chardet/codingstatemachine.py b/robot/lib/python3.8/site-packages/chardet/codingstatemachine.py new file mode 100644 index 0000000000000000000000000000000000000000..68fba44f14366c448f13db3cf9cf1665af2e498c --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/codingstatemachine.py @@ -0,0 +1,88 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import logging + +from .enums import MachineState + + +class CodingStateMachine(object): + """ + A state machine to verify a byte sequence for a particular encoding. For + each byte the detector receives, it will feed that byte to every active + state machine available, one byte at a time. The state machine changes its + state based on its previous state and the byte it receives. There are 3 + states in a state machine that are of interest to an auto-detector: + + START state: This is the state to start with, or a legal byte sequence + (i.e. a valid code point) for character has been identified. + + ME state: This indicates that the state machine identified a byte sequence + that is specific to the charset it is designed for and that + there is no other possible encoding which can contain this byte + sequence. This will to lead to an immediate positive answer for + the detector. + + ERROR state: This indicates the state machine identified an illegal byte + sequence for that encoding. This will lead to an immediate + negative answer for this encoding. Detector will exclude this + encoding from consideration from here on. + """ + def __init__(self, sm): + self._model = sm + self._curr_byte_pos = 0 + self._curr_char_len = 0 + self._curr_state = None + self.logger = logging.getLogger(__name__) + self.reset() + + def reset(self): + self._curr_state = MachineState.START + + def next_state(self, c): + # for each byte we get its class + # if it is first byte, we also get byte length + byte_class = self._model['class_table'][c] + if self._curr_state == MachineState.START: + self._curr_byte_pos = 0 + self._curr_char_len = self._model['char_len_table'][byte_class] + # from byte's class and state_table, we get its next state + curr_state = (self._curr_state * self._model['class_factor'] + + byte_class) + self._curr_state = self._model['state_table'][curr_state] + self._curr_byte_pos += 1 + return self._curr_state + + def get_current_charlen(self): + return self._curr_char_len + + def get_coding_state_machine(self): + return self._model['name'] + + @property + def language(self): + return self._model['language'] diff --git a/robot/lib/python3.8/site-packages/chardet/compat.py b/robot/lib/python3.8/site-packages/chardet/compat.py new file mode 100644 index 0000000000000000000000000000000000000000..ddd74687c02a12ecc296ee803997a345e984bc9d --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/compat.py @@ -0,0 +1,34 @@ +######################## BEGIN LICENSE BLOCK ######################## +# Contributor(s): +# Dan Blanchard +# Ian Cordasco +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import sys + + +if sys.version_info < (3, 0): + PY2 = True + PY3 = False + base_str = (str, unicode) + text_type = unicode +else: + PY2 = False + PY3 = True + base_str = (bytes, str) + text_type = str diff --git a/robot/lib/python3.8/site-packages/chardet/cp949prober.py b/robot/lib/python3.8/site-packages/chardet/cp949prober.py new file mode 100644 index 0000000000000000000000000000000000000000..efd793abca4bf496001a4e46a67557e5a6f16bba --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/cp949prober.py @@ -0,0 +1,49 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .chardistribution import EUCKRDistributionAnalysis +from .codingstatemachine import CodingStateMachine +from .mbcharsetprober import MultiByteCharSetProber +from .mbcssm import CP949_SM_MODEL + + +class CP949Prober(MultiByteCharSetProber): + def __init__(self): + super(CP949Prober, self).__init__() + self.coding_sm = CodingStateMachine(CP949_SM_MODEL) + # NOTE: CP949 is a superset of EUC-KR, so the distribution should be + # not different. + self.distribution_analyzer = EUCKRDistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "CP949" + + @property + def language(self): + return "Korean" diff --git a/robot/lib/python3.8/site-packages/chardet/enums.py b/robot/lib/python3.8/site-packages/chardet/enums.py new file mode 100644 index 0000000000000000000000000000000000000000..04512072251c429e63ed110cdbafaf4b3cca3412 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/enums.py @@ -0,0 +1,76 @@ +""" +All of the Enums that are used throughout the chardet package. + +:author: Dan Blanchard (dan.blanchard@gmail.com) +""" + + +class InputState(object): + """ + This enum represents the different states a universal detector can be in. + """ + PURE_ASCII = 0 + ESC_ASCII = 1 + HIGH_BYTE = 2 + + +class LanguageFilter(object): + """ + This enum represents the different language filters we can apply to a + ``UniversalDetector``. + """ + CHINESE_SIMPLIFIED = 0x01 + CHINESE_TRADITIONAL = 0x02 + JAPANESE = 0x04 + KOREAN = 0x08 + NON_CJK = 0x10 + ALL = 0x1F + CHINESE = CHINESE_SIMPLIFIED | CHINESE_TRADITIONAL + CJK = CHINESE | JAPANESE | KOREAN + + +class ProbingState(object): + """ + This enum represents the different states a prober can be in. + """ + DETECTING = 0 + FOUND_IT = 1 + NOT_ME = 2 + + +class MachineState(object): + """ + This enum represents the different states a state machine can be in. + """ + START = 0 + ERROR = 1 + ITS_ME = 2 + + +class SequenceLikelihood(object): + """ + This enum represents the likelihood of a character following the previous one. + """ + NEGATIVE = 0 + UNLIKELY = 1 + LIKELY = 2 + POSITIVE = 3 + + @classmethod + def get_num_categories(cls): + """:returns: The number of likelihood categories in the enum.""" + return 4 + + +class CharacterCategory(object): + """ + This enum represents the different categories language models for + ``SingleByteCharsetProber`` put characters into. + + Anything less than CONTROL is considered a letter. + """ + UNDEFINED = 255 + LINE_BREAK = 254 + SYMBOL = 253 + DIGIT = 252 + CONTROL = 251 diff --git a/robot/lib/python3.8/site-packages/chardet/escprober.py b/robot/lib/python3.8/site-packages/chardet/escprober.py new file mode 100644 index 0000000000000000000000000000000000000000..c70493f2b131b32378612044f30173eabbfbc3f4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/escprober.py @@ -0,0 +1,101 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .codingstatemachine import CodingStateMachine +from .enums import LanguageFilter, ProbingState, MachineState +from .escsm import (HZ_SM_MODEL, ISO2022CN_SM_MODEL, ISO2022JP_SM_MODEL, + ISO2022KR_SM_MODEL) + + +class EscCharSetProber(CharSetProber): + """ + This CharSetProber uses a "code scheme" approach for detecting encodings, + whereby easily recognizable escape or shift sequences are relied on to + identify these encodings. + """ + + def __init__(self, lang_filter=None): + super(EscCharSetProber, self).__init__(lang_filter=lang_filter) + self.coding_sm = [] + if self.lang_filter & LanguageFilter.CHINESE_SIMPLIFIED: + self.coding_sm.append(CodingStateMachine(HZ_SM_MODEL)) + self.coding_sm.append(CodingStateMachine(ISO2022CN_SM_MODEL)) + if self.lang_filter & LanguageFilter.JAPANESE: + self.coding_sm.append(CodingStateMachine(ISO2022JP_SM_MODEL)) + if self.lang_filter & LanguageFilter.KOREAN: + self.coding_sm.append(CodingStateMachine(ISO2022KR_SM_MODEL)) + self.active_sm_count = None + self._detected_charset = None + self._detected_language = None + self._state = None + self.reset() + + def reset(self): + super(EscCharSetProber, self).reset() + for coding_sm in self.coding_sm: + if not coding_sm: + continue + coding_sm.active = True + coding_sm.reset() + self.active_sm_count = len(self.coding_sm) + self._detected_charset = None + self._detected_language = None + + @property + def charset_name(self): + return self._detected_charset + + @property + def language(self): + return self._detected_language + + def get_confidence(self): + if self._detected_charset: + return 0.99 + else: + return 0.00 + + def feed(self, byte_str): + for c in byte_str: + for coding_sm in self.coding_sm: + if not coding_sm or not coding_sm.active: + continue + coding_state = coding_sm.next_state(c) + if coding_state == MachineState.ERROR: + coding_sm.active = False + self.active_sm_count -= 1 + if self.active_sm_count <= 0: + self._state = ProbingState.NOT_ME + return self.state + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + self._detected_charset = coding_sm.get_coding_state_machine() + self._detected_language = coding_sm.language + return self.state + + return self.state diff --git a/robot/lib/python3.8/site-packages/chardet/escsm.py b/robot/lib/python3.8/site-packages/chardet/escsm.py new file mode 100644 index 0000000000000000000000000000000000000000..0069523a04969dd920ea4b43a497162157729174 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/escsm.py @@ -0,0 +1,246 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .enums import MachineState + +HZ_CLS = ( +1,0,0,0,0,0,0,0, # 00 - 07 +0,0,0,0,0,0,0,0, # 08 - 0f +0,0,0,0,0,0,0,0, # 10 - 17 +0,0,0,1,0,0,0,0, # 18 - 1f +0,0,0,0,0,0,0,0, # 20 - 27 +0,0,0,0,0,0,0,0, # 28 - 2f +0,0,0,0,0,0,0,0, # 30 - 37 +0,0,0,0,0,0,0,0, # 38 - 3f +0,0,0,0,0,0,0,0, # 40 - 47 +0,0,0,0,0,0,0,0, # 48 - 4f +0,0,0,0,0,0,0,0, # 50 - 57 +0,0,0,0,0,0,0,0, # 58 - 5f +0,0,0,0,0,0,0,0, # 60 - 67 +0,0,0,0,0,0,0,0, # 68 - 6f +0,0,0,0,0,0,0,0, # 70 - 77 +0,0,0,4,0,5,2,0, # 78 - 7f +1,1,1,1,1,1,1,1, # 80 - 87 +1,1,1,1,1,1,1,1, # 88 - 8f +1,1,1,1,1,1,1,1, # 90 - 97 +1,1,1,1,1,1,1,1, # 98 - 9f +1,1,1,1,1,1,1,1, # a0 - a7 +1,1,1,1,1,1,1,1, # a8 - af +1,1,1,1,1,1,1,1, # b0 - b7 +1,1,1,1,1,1,1,1, # b8 - bf +1,1,1,1,1,1,1,1, # c0 - c7 +1,1,1,1,1,1,1,1, # c8 - cf +1,1,1,1,1,1,1,1, # d0 - d7 +1,1,1,1,1,1,1,1, # d8 - df +1,1,1,1,1,1,1,1, # e0 - e7 +1,1,1,1,1,1,1,1, # e8 - ef +1,1,1,1,1,1,1,1, # f0 - f7 +1,1,1,1,1,1,1,1, # f8 - ff +) + +HZ_ST = ( +MachineState.START,MachineState.ERROR, 3,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,# 00-07 +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 08-0f +MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START, 4,MachineState.ERROR,# 10-17 + 5,MachineState.ERROR, 6,MachineState.ERROR, 5, 5, 4,MachineState.ERROR,# 18-1f + 4,MachineState.ERROR, 4, 4, 4,MachineState.ERROR, 4,MachineState.ERROR,# 20-27 + 4,MachineState.ITS_ME,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 28-2f +) + +HZ_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0) + +HZ_SM_MODEL = {'class_table': HZ_CLS, + 'class_factor': 6, + 'state_table': HZ_ST, + 'char_len_table': HZ_CHAR_LEN_TABLE, + 'name': "HZ-GB-2312", + 'language': 'Chinese'} + +ISO2022CN_CLS = ( +2,0,0,0,0,0,0,0, # 00 - 07 +0,0,0,0,0,0,0,0, # 08 - 0f +0,0,0,0,0,0,0,0, # 10 - 17 +0,0,0,1,0,0,0,0, # 18 - 1f +0,0,0,0,0,0,0,0, # 20 - 27 +0,3,0,0,0,0,0,0, # 28 - 2f +0,0,0,0,0,0,0,0, # 30 - 37 +0,0,0,0,0,0,0,0, # 38 - 3f +0,0,0,4,0,0,0,0, # 40 - 47 +0,0,0,0,0,0,0,0, # 48 - 4f +0,0,0,0,0,0,0,0, # 50 - 57 +0,0,0,0,0,0,0,0, # 58 - 5f +0,0,0,0,0,0,0,0, # 60 - 67 +0,0,0,0,0,0,0,0, # 68 - 6f +0,0,0,0,0,0,0,0, # 70 - 77 +0,0,0,0,0,0,0,0, # 78 - 7f +2,2,2,2,2,2,2,2, # 80 - 87 +2,2,2,2,2,2,2,2, # 88 - 8f +2,2,2,2,2,2,2,2, # 90 - 97 +2,2,2,2,2,2,2,2, # 98 - 9f +2,2,2,2,2,2,2,2, # a0 - a7 +2,2,2,2,2,2,2,2, # a8 - af +2,2,2,2,2,2,2,2, # b0 - b7 +2,2,2,2,2,2,2,2, # b8 - bf +2,2,2,2,2,2,2,2, # c0 - c7 +2,2,2,2,2,2,2,2, # c8 - cf +2,2,2,2,2,2,2,2, # d0 - d7 +2,2,2,2,2,2,2,2, # d8 - df +2,2,2,2,2,2,2,2, # e0 - e7 +2,2,2,2,2,2,2,2, # e8 - ef +2,2,2,2,2,2,2,2, # f0 - f7 +2,2,2,2,2,2,2,2, # f8 - ff +) + +ISO2022CN_ST = ( +MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 00-07 +MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 08-0f +MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 10-17 +MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,# 18-1f +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 20-27 + 5, 6,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 28-2f +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 30-37 +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,# 38-3f +) + +ISO2022CN_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0, 0, 0, 0) + +ISO2022CN_SM_MODEL = {'class_table': ISO2022CN_CLS, + 'class_factor': 9, + 'state_table': ISO2022CN_ST, + 'char_len_table': ISO2022CN_CHAR_LEN_TABLE, + 'name': "ISO-2022-CN", + 'language': 'Chinese'} + +ISO2022JP_CLS = ( +2,0,0,0,0,0,0,0, # 00 - 07 +0,0,0,0,0,0,2,2, # 08 - 0f +0,0,0,0,0,0,0,0, # 10 - 17 +0,0,0,1,0,0,0,0, # 18 - 1f +0,0,0,0,7,0,0,0, # 20 - 27 +3,0,0,0,0,0,0,0, # 28 - 2f +0,0,0,0,0,0,0,0, # 30 - 37 +0,0,0,0,0,0,0,0, # 38 - 3f +6,0,4,0,8,0,0,0, # 40 - 47 +0,9,5,0,0,0,0,0, # 48 - 4f +0,0,0,0,0,0,0,0, # 50 - 57 +0,0,0,0,0,0,0,0, # 58 - 5f +0,0,0,0,0,0,0,0, # 60 - 67 +0,0,0,0,0,0,0,0, # 68 - 6f +0,0,0,0,0,0,0,0, # 70 - 77 +0,0,0,0,0,0,0,0, # 78 - 7f +2,2,2,2,2,2,2,2, # 80 - 87 +2,2,2,2,2,2,2,2, # 88 - 8f +2,2,2,2,2,2,2,2, # 90 - 97 +2,2,2,2,2,2,2,2, # 98 - 9f +2,2,2,2,2,2,2,2, # a0 - a7 +2,2,2,2,2,2,2,2, # a8 - af +2,2,2,2,2,2,2,2, # b0 - b7 +2,2,2,2,2,2,2,2, # b8 - bf +2,2,2,2,2,2,2,2, # c0 - c7 +2,2,2,2,2,2,2,2, # c8 - cf +2,2,2,2,2,2,2,2, # d0 - d7 +2,2,2,2,2,2,2,2, # d8 - df +2,2,2,2,2,2,2,2, # e0 - e7 +2,2,2,2,2,2,2,2, # e8 - ef +2,2,2,2,2,2,2,2, # f0 - f7 +2,2,2,2,2,2,2,2, # f8 - ff +) + +ISO2022JP_ST = ( +MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 00-07 +MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 08-0f +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 10-17 +MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,# 18-1f +MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,MachineState.ERROR,# 20-27 +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 6,MachineState.ITS_ME,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,# 28-2f +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,# 30-37 +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 38-3f +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,MachineState.START,# 40-47 +) + +ISO2022JP_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + +ISO2022JP_SM_MODEL = {'class_table': ISO2022JP_CLS, + 'class_factor': 10, + 'state_table': ISO2022JP_ST, + 'char_len_table': ISO2022JP_CHAR_LEN_TABLE, + 'name': "ISO-2022-JP", + 'language': 'Japanese'} + +ISO2022KR_CLS = ( +2,0,0,0,0,0,0,0, # 00 - 07 +0,0,0,0,0,0,0,0, # 08 - 0f +0,0,0,0,0,0,0,0, # 10 - 17 +0,0,0,1,0,0,0,0, # 18 - 1f +0,0,0,0,3,0,0,0, # 20 - 27 +0,4,0,0,0,0,0,0, # 28 - 2f +0,0,0,0,0,0,0,0, # 30 - 37 +0,0,0,0,0,0,0,0, # 38 - 3f +0,0,0,5,0,0,0,0, # 40 - 47 +0,0,0,0,0,0,0,0, # 48 - 4f +0,0,0,0,0,0,0,0, # 50 - 57 +0,0,0,0,0,0,0,0, # 58 - 5f +0,0,0,0,0,0,0,0, # 60 - 67 +0,0,0,0,0,0,0,0, # 68 - 6f +0,0,0,0,0,0,0,0, # 70 - 77 +0,0,0,0,0,0,0,0, # 78 - 7f +2,2,2,2,2,2,2,2, # 80 - 87 +2,2,2,2,2,2,2,2, # 88 - 8f +2,2,2,2,2,2,2,2, # 90 - 97 +2,2,2,2,2,2,2,2, # 98 - 9f +2,2,2,2,2,2,2,2, # a0 - a7 +2,2,2,2,2,2,2,2, # a8 - af +2,2,2,2,2,2,2,2, # b0 - b7 +2,2,2,2,2,2,2,2, # b8 - bf +2,2,2,2,2,2,2,2, # c0 - c7 +2,2,2,2,2,2,2,2, # c8 - cf +2,2,2,2,2,2,2,2, # d0 - d7 +2,2,2,2,2,2,2,2, # d8 - df +2,2,2,2,2,2,2,2, # e0 - e7 +2,2,2,2,2,2,2,2, # e8 - ef +2,2,2,2,2,2,2,2, # f0 - f7 +2,2,2,2,2,2,2,2, # f8 - ff +) + +ISO2022KR_ST = ( +MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,# 00-07 +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 08-0f +MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,MachineState.ERROR,# 10-17 +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 18-1f +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 20-27 +) + +ISO2022KR_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0) + +ISO2022KR_SM_MODEL = {'class_table': ISO2022KR_CLS, + 'class_factor': 6, + 'state_table': ISO2022KR_ST, + 'char_len_table': ISO2022KR_CHAR_LEN_TABLE, + 'name': "ISO-2022-KR", + 'language': 'Korean'} + + diff --git a/robot/lib/python3.8/site-packages/chardet/eucjpprober.py b/robot/lib/python3.8/site-packages/chardet/eucjpprober.py new file mode 100644 index 0000000000000000000000000000000000000000..20ce8f7d15bad9d48a3e0363de2286093a4a28cc --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/eucjpprober.py @@ -0,0 +1,92 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .enums import ProbingState, MachineState +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCJPDistributionAnalysis +from .jpcntx import EUCJPContextAnalysis +from .mbcssm import EUCJP_SM_MODEL + + +class EUCJPProber(MultiByteCharSetProber): + def __init__(self): + super(EUCJPProber, self).__init__() + self.coding_sm = CodingStateMachine(EUCJP_SM_MODEL) + self.distribution_analyzer = EUCJPDistributionAnalysis() + self.context_analyzer = EUCJPContextAnalysis() + self.reset() + + def reset(self): + super(EUCJPProber, self).reset() + self.context_analyzer.reset() + + @property + def charset_name(self): + return "EUC-JP" + + @property + def language(self): + return "Japanese" + + def feed(self, byte_str): + for i in range(len(byte_str)): + # PY3K: byte_str is a byte array, so byte_str[i] is an int, not a byte + coding_state = self.coding_sm.next_state(byte_str[i]) + if coding_state == MachineState.ERROR: + self.logger.debug('%s %s prober hit error at byte %s', + self.charset_name, self.language, i) + self._state = ProbingState.NOT_ME + break + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + break + elif coding_state == MachineState.START: + char_len = self.coding_sm.get_current_charlen() + if i == 0: + self._last_char[1] = byte_str[0] + self.context_analyzer.feed(self._last_char, char_len) + self.distribution_analyzer.feed(self._last_char, char_len) + else: + self.context_analyzer.feed(byte_str[i - 1:i + 1], + char_len) + self.distribution_analyzer.feed(byte_str[i - 1:i + 1], + char_len) + + self._last_char[0] = byte_str[-1] + + if self.state == ProbingState.DETECTING: + if (self.context_analyzer.got_enough_data() and + (self.get_confidence() > self.SHORTCUT_THRESHOLD)): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + context_conf = self.context_analyzer.get_confidence() + distrib_conf = self.distribution_analyzer.get_confidence() + return max(context_conf, distrib_conf) diff --git a/robot/lib/python3.8/site-packages/chardet/euckrfreq.py b/robot/lib/python3.8/site-packages/chardet/euckrfreq.py new file mode 100644 index 0000000000000000000000000000000000000000..b68078cb9680de4cca65b1145632a37c5e751c38 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/euckrfreq.py @@ -0,0 +1,195 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# Sampling from about 20M text materials include literature and computer technology + +# 128 --> 0.79 +# 256 --> 0.92 +# 512 --> 0.986 +# 1024 --> 0.99944 +# 2048 --> 0.99999 +# +# Idea Distribution Ratio = 0.98653 / (1-0.98653) = 73.24 +# Random Distribution Ration = 512 / (2350-512) = 0.279. +# +# Typical Distribution Ratio + +EUCKR_TYPICAL_DISTRIBUTION_RATIO = 6.0 + +EUCKR_TABLE_SIZE = 2352 + +# Char to FreqOrder table , +EUCKR_CHAR_TO_FREQ_ORDER = ( + 13, 130, 120,1396, 481,1719,1720, 328, 609, 212,1721, 707, 400, 299,1722, 87, +1397,1723, 104, 536,1117,1203,1724,1267, 685,1268, 508,1725,1726,1727,1728,1398, +1399,1729,1730,1731, 141, 621, 326,1057, 368,1732, 267, 488, 20,1733,1269,1734, + 945,1400,1735, 47, 904,1270,1736,1737, 773, 248,1738, 409, 313, 786, 429,1739, + 116, 987, 813,1401, 683, 75,1204, 145,1740,1741,1742,1743, 16, 847, 667, 622, + 708,1744,1745,1746, 966, 787, 304, 129,1747, 60, 820, 123, 676,1748,1749,1750, +1751, 617,1752, 626,1753,1754,1755,1756, 653,1757,1758,1759,1760,1761,1762, 856, + 344,1763,1764,1765,1766, 89, 401, 418, 806, 905, 848,1767,1768,1769, 946,1205, + 709,1770,1118,1771, 241,1772,1773,1774,1271,1775, 569,1776, 999,1777,1778,1779, +1780, 337, 751,1058, 28, 628, 254,1781, 177, 906, 270, 349, 891,1079,1782, 19, +1783, 379,1784, 315,1785, 629, 754,1402, 559,1786, 636, 203,1206,1787, 710, 567, +1788, 935, 814,1789,1790,1207, 766, 528,1791,1792,1208,1793,1794,1795,1796,1797, +1403,1798,1799, 533,1059,1404,1405,1156,1406, 936, 884,1080,1800, 351,1801,1802, +1803,1804,1805, 801,1806,1807,1808,1119,1809,1157, 714, 474,1407,1810, 298, 899, + 885,1811,1120, 802,1158,1812, 892,1813,1814,1408, 659,1815,1816,1121,1817,1818, +1819,1820,1821,1822, 319,1823, 594, 545,1824, 815, 937,1209,1825,1826, 573,1409, +1022,1827,1210,1828,1829,1830,1831,1832,1833, 556, 722, 807,1122,1060,1834, 697, +1835, 900, 557, 715,1836,1410, 540,1411, 752,1159, 294, 597,1211, 976, 803, 770, +1412,1837,1838, 39, 794,1413, 358,1839, 371, 925,1840, 453, 661, 788, 531, 723, + 544,1023,1081, 869, 91,1841, 392, 430, 790, 602,1414, 677,1082, 457,1415,1416, +1842,1843, 475, 327,1024,1417, 795, 121,1844, 733, 403,1418,1845,1846,1847, 300, + 119, 711,1212, 627,1848,1272, 207,1849,1850, 796,1213, 382,1851, 519,1852,1083, + 893,1853,1854,1855, 367, 809, 487, 671,1856, 663,1857,1858, 956, 471, 306, 857, +1859,1860,1160,1084,1861,1862,1863,1864,1865,1061,1866,1867,1868,1869,1870,1871, + 282, 96, 574,1872, 502,1085,1873,1214,1874, 907,1875,1876, 827, 977,1419,1420, +1421, 268,1877,1422,1878,1879,1880, 308,1881, 2, 537,1882,1883,1215,1884,1885, + 127, 791,1886,1273,1423,1887, 34, 336, 404, 643,1888, 571, 654, 894, 840,1889, + 0, 886,1274, 122, 575, 260, 908, 938,1890,1275, 410, 316,1891,1892, 100,1893, +1894,1123, 48,1161,1124,1025,1895, 633, 901,1276,1896,1897, 115, 816,1898, 317, +1899, 694,1900, 909, 734,1424, 572, 866,1425, 691, 85, 524,1010, 543, 394, 841, +1901,1902,1903,1026,1904,1905,1906,1907,1908,1909, 30, 451, 651, 988, 310,1910, +1911,1426, 810,1216, 93,1912,1913,1277,1217,1914, 858, 759, 45, 58, 181, 610, + 269,1915,1916, 131,1062, 551, 443,1000, 821,1427, 957, 895,1086,1917,1918, 375, +1919, 359,1920, 687,1921, 822,1922, 293,1923,1924, 40, 662, 118, 692, 29, 939, + 887, 640, 482, 174,1925, 69,1162, 728,1428, 910,1926,1278,1218,1279, 386, 870, + 217, 854,1163, 823,1927,1928,1929,1930, 834,1931, 78,1932, 859,1933,1063,1934, +1935,1936,1937, 438,1164, 208, 595,1938,1939,1940,1941,1219,1125,1942, 280, 888, +1429,1430,1220,1431,1943,1944,1945,1946,1947,1280, 150, 510,1432,1948,1949,1950, +1951,1952,1953,1954,1011,1087,1955,1433,1043,1956, 881,1957, 614, 958,1064,1065, +1221,1958, 638,1001, 860, 967, 896,1434, 989, 492, 553,1281,1165,1959,1282,1002, +1283,1222,1960,1961,1962,1963, 36, 383, 228, 753, 247, 454,1964, 876, 678,1965, +1966,1284, 126, 464, 490, 835, 136, 672, 529, 940,1088,1435, 473,1967,1968, 467, + 50, 390, 227, 587, 279, 378, 598, 792, 968, 240, 151, 160, 849, 882,1126,1285, + 639,1044, 133, 140, 288, 360, 811, 563,1027, 561, 142, 523,1969,1970,1971, 7, + 103, 296, 439, 407, 506, 634, 990,1972,1973,1974,1975, 645,1976,1977,1978,1979, +1980,1981, 236,1982,1436,1983,1984,1089, 192, 828, 618, 518,1166, 333,1127,1985, + 818,1223,1986,1987,1988,1989,1990,1991,1992,1993, 342,1128,1286, 746, 842,1994, +1995, 560, 223,1287, 98, 8, 189, 650, 978,1288,1996,1437,1997, 17, 345, 250, + 423, 277, 234, 512, 226, 97, 289, 42, 167,1998, 201,1999,2000, 843, 836, 824, + 532, 338, 783,1090, 182, 576, 436,1438,1439, 527, 500,2001, 947, 889,2002,2003, +2004,2005, 262, 600, 314, 447,2006, 547,2007, 693, 738,1129,2008, 71,1440, 745, + 619, 688,2009, 829,2010,2011, 147,2012, 33, 948,2013,2014, 74, 224,2015, 61, + 191, 918, 399, 637,2016,1028,1130, 257, 902,2017,2018,2019,2020,2021,2022,2023, +2024,2025,2026, 837,2027,2028,2029,2030, 179, 874, 591, 52, 724, 246,2031,2032, +2033,2034,1167, 969,2035,1289, 630, 605, 911,1091,1168,2036,2037,2038,1441, 912, +2039, 623,2040,2041, 253,1169,1290,2042,1442, 146, 620, 611, 577, 433,2043,1224, + 719,1170, 959, 440, 437, 534, 84, 388, 480,1131, 159, 220, 198, 679,2044,1012, + 819,1066,1443, 113,1225, 194, 318,1003,1029,2045,2046,2047,2048,1067,2049,2050, +2051,2052,2053, 59, 913, 112,2054, 632,2055, 455, 144, 739,1291,2056, 273, 681, + 499,2057, 448,2058,2059, 760,2060,2061, 970, 384, 169, 245,1132,2062,2063, 414, +1444,2064,2065, 41, 235,2066, 157, 252, 877, 568, 919, 789, 580,2067, 725,2068, +2069,1292,2070,2071,1445,2072,1446,2073,2074, 55, 588, 66,1447, 271,1092,2075, +1226,2076, 960,1013, 372,2077,2078,2079,2080,2081,1293,2082,2083,2084,2085, 850, +2086,2087,2088,2089,2090, 186,2091,1068, 180,2092,2093,2094, 109,1227, 522, 606, +2095, 867,1448,1093, 991,1171, 926, 353,1133,2096, 581,2097,2098,2099,1294,1449, +1450,2100, 596,1172,1014,1228,2101,1451,1295,1173,1229,2102,2103,1296,1134,1452, + 949,1135,2104,2105,1094,1453,1454,1455,2106,1095,2107,2108,2109,2110,2111,2112, +2113,2114,2115,2116,2117, 804,2118,2119,1230,1231, 805,1456, 405,1136,2120,2121, +2122,2123,2124, 720, 701,1297, 992,1457, 927,1004,2125,2126,2127,2128,2129,2130, + 22, 417,2131, 303,2132, 385,2133, 971, 520, 513,2134,1174, 73,1096, 231, 274, + 962,1458, 673,2135,1459,2136, 152,1137,2137,2138,2139,2140,1005,1138,1460,1139, +2141,2142,2143,2144, 11, 374, 844,2145, 154,1232, 46,1461,2146, 838, 830, 721, +1233, 106,2147, 90, 428, 462, 578, 566,1175, 352,2148,2149, 538,1234, 124,1298, +2150,1462, 761, 565,2151, 686,2152, 649,2153, 72, 173,2154, 460, 415,2155,1463, +2156,1235, 305,2157,2158,2159,2160,2161,2162, 579,2163,2164,2165,2166,2167, 747, +2168,2169,2170,2171,1464, 669,2172,2173,2174,2175,2176,1465,2177, 23, 530, 285, +2178, 335, 729,2179, 397,2180,2181,2182,1030,2183,2184, 698,2185,2186, 325,2187, +2188, 369,2189, 799,1097,1015, 348,2190,1069, 680,2191, 851,1466,2192,2193, 10, +2194, 613, 424,2195, 979, 108, 449, 589, 27, 172, 81,1031, 80, 774, 281, 350, +1032, 525, 301, 582,1176,2196, 674,1045,2197,2198,1467, 730, 762,2199,2200,2201, +2202,1468,2203, 993,2204,2205, 266,1070, 963,1140,2206,2207,2208, 664,1098, 972, +2209,2210,2211,1177,1469,1470, 871,2212,2213,2214,2215,2216,1471,2217,2218,2219, +2220,2221,2222,2223,2224,2225,2226,2227,1472,1236,2228,2229,2230,2231,2232,2233, +2234,2235,1299,2236,2237, 200,2238, 477, 373,2239,2240, 731, 825, 777,2241,2242, +2243, 521, 486, 548,2244,2245,2246,1473,1300, 53, 549, 137, 875, 76, 158,2247, +1301,1474, 469, 396,1016, 278, 712,2248, 321, 442, 503, 767, 744, 941,1237,1178, +1475,2249, 82, 178,1141,1179, 973,2250,1302,2251, 297,2252,2253, 570,2254,2255, +2256, 18, 450, 206,2257, 290, 292,1142,2258, 511, 162, 99, 346, 164, 735,2259, +1476,1477, 4, 554, 343, 798,1099,2260,1100,2261, 43, 171,1303, 139, 215,2262, +2263, 717, 775,2264,1033, 322, 216,2265, 831,2266, 149,2267,1304,2268,2269, 702, +1238, 135, 845, 347, 309,2270, 484,2271, 878, 655, 238,1006,1478,2272, 67,2273, + 295,2274,2275, 461,2276, 478, 942, 412,2277,1034,2278,2279,2280, 265,2281, 541, +2282,2283,2284,2285,2286, 70, 852,1071,2287,2288,2289,2290, 21, 56, 509, 117, + 432,2291,2292, 331, 980, 552,1101, 148, 284, 105, 393,1180,1239, 755,2293, 187, +2294,1046,1479,2295, 340,2296, 63,1047, 230,2297,2298,1305, 763,1306, 101, 800, + 808, 494,2299,2300,2301, 903,2302, 37,1072, 14, 5,2303, 79, 675,2304, 312, +2305,2306,2307,2308,2309,1480, 6,1307,2310,2311,2312, 1, 470, 35, 24, 229, +2313, 695, 210, 86, 778, 15, 784, 592, 779, 32, 77, 855, 964,2314, 259,2315, + 501, 380,2316,2317, 83, 981, 153, 689,1308,1481,1482,1483,2318,2319, 716,1484, +2320,2321,2322,2323,2324,2325,1485,2326,2327, 128, 57, 68, 261,1048, 211, 170, +1240, 31,2328, 51, 435, 742,2329,2330,2331, 635,2332, 264, 456,2333,2334,2335, + 425,2336,1486, 143, 507, 263, 943,2337, 363, 920,1487, 256,1488,1102, 243, 601, +1489,2338,2339,2340,2341,2342,2343,2344, 861,2345,2346,2347,2348,2349,2350, 395, +2351,1490,1491, 62, 535, 166, 225,2352,2353, 668, 419,1241, 138, 604, 928,2354, +1181,2355,1492,1493,2356,2357,2358,1143,2359, 696,2360, 387, 307,1309, 682, 476, +2361,2362, 332, 12, 222, 156,2363, 232,2364, 641, 276, 656, 517,1494,1495,1035, + 416, 736,1496,2365,1017, 586,2366,2367,2368,1497,2369, 242,2370,2371,2372,1498, +2373, 965, 713,2374,2375,2376,2377, 740, 982,1499, 944,1500,1007,2378,2379,1310, +1501,2380,2381,2382, 785, 329,2383,2384,1502,2385,2386,2387, 932,2388,1503,2389, +2390,2391,2392,1242,2393,2394,2395,2396,2397, 994, 950,2398,2399,2400,2401,1504, +1311,2402,2403,2404,2405,1049, 749,2406,2407, 853, 718,1144,1312,2408,1182,1505, +2409,2410, 255, 516, 479, 564, 550, 214,1506,1507,1313, 413, 239, 444, 339,1145, +1036,1508,1509,1314,1037,1510,1315,2411,1511,2412,2413,2414, 176, 703, 497, 624, + 593, 921, 302,2415, 341, 165,1103,1512,2416,1513,2417,2418,2419, 376,2420, 700, +2421,2422,2423, 258, 768,1316,2424,1183,2425, 995, 608,2426,2427,2428,2429, 221, +2430,2431,2432,2433,2434,2435,2436,2437, 195, 323, 726, 188, 897, 983,1317, 377, + 644,1050, 879,2438, 452,2439,2440,2441,2442,2443,2444, 914,2445,2446,2447,2448, + 915, 489,2449,1514,1184,2450,2451, 515, 64, 427, 495,2452, 583,2453, 483, 485, +1038, 562, 213,1515, 748, 666,2454,2455,2456,2457, 334,2458, 780, 996,1008, 705, +1243,2459,2460,2461,2462,2463, 114,2464, 493,1146, 366, 163,1516, 961,1104,2465, + 291,2466,1318,1105,2467,1517, 365,2468, 355, 951,1244,2469,1319,2470, 631,2471, +2472, 218,1320, 364, 320, 756,1518,1519,1321,1520,1322,2473,2474,2475,2476, 997, +2477,2478,2479,2480, 665,1185,2481, 916,1521,2482,2483,2484, 584, 684,2485,2486, + 797,2487,1051,1186,2488,2489,2490,1522,2491,2492, 370,2493,1039,1187, 65,2494, + 434, 205, 463,1188,2495, 125, 812, 391, 402, 826, 699, 286, 398, 155, 781, 771, + 585,2496, 590, 505,1073,2497, 599, 244, 219, 917,1018, 952, 646,1523,2498,1323, +2499,2500, 49, 984, 354, 741,2501, 625,2502,1324,2503,1019, 190, 357, 757, 491, + 95, 782, 868,2504,2505,2506,2507,2508,2509, 134,1524,1074, 422,1525, 898,2510, + 161,2511,2512,2513,2514, 769,2515,1526,2516,2517, 411,1325,2518, 472,1527,2519, +2520,2521,2522,2523,2524, 985,2525,2526,2527,2528,2529,2530, 764,2531,1245,2532, +2533, 25, 204, 311,2534, 496,2535,1052,2536,2537,2538,2539,2540,2541,2542, 199, + 704, 504, 468, 758, 657,1528, 196, 44, 839,1246, 272, 750,2543, 765, 862,2544, +2545,1326,2546, 132, 615, 933,2547, 732,2548,2549,2550,1189,1529,2551, 283,1247, +1053, 607, 929,2552,2553,2554, 930, 183, 872, 616,1040,1147,2555,1148,1020, 441, + 249,1075,2556,2557,2558, 466, 743,2559,2560,2561, 92, 514, 426, 420, 526,2562, +2563,2564,2565,2566,2567,2568, 185,2569,2570,2571,2572, 776,1530, 658,2573, 362, +2574, 361, 922,1076, 793,2575,2576,2577,2578,2579,2580,1531, 251,2581,2582,2583, +2584,1532, 54, 612, 237,1327,2585,2586, 275, 408, 647, 111,2587,1533,1106, 465, + 3, 458, 9, 38,2588, 107, 110, 890, 209, 26, 737, 498,2589,1534,2590, 431, + 202, 88,1535, 356, 287,1107, 660,1149,2591, 381,1536, 986,1150, 445,1248,1151, + 974,2592,2593, 846,2594, 446, 953, 184,1249,1250, 727,2595, 923, 193, 883,2596, +2597,2598, 102, 324, 539, 817,2599, 421,1041,2600, 832,2601, 94, 175, 197, 406, +2602, 459,2603,2604,2605,2606,2607, 330, 555,2608,2609,2610, 706,1108, 389,2611, +2612,2613,2614, 233,2615, 833, 558, 931, 954,1251,2616,2617,1537, 546,2618,2619, +1009,2620,2621,2622,1538, 690,1328,2623, 955,2624,1539,2625,2626, 772,2627,2628, +2629,2630,2631, 924, 648, 863, 603,2632,2633, 934,1540, 864, 865,2634, 642,1042, + 670,1190,2635,2636,2637,2638, 168,2639, 652, 873, 542,1054,1541,2640,2641,2642, # 512, 256 +) + diff --git a/robot/lib/python3.8/site-packages/chardet/euckrprober.py b/robot/lib/python3.8/site-packages/chardet/euckrprober.py new file mode 100644 index 0000000000000000000000000000000000000000..345a060d0230ada58f769b0f6b55f43a428fc3bd --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/euckrprober.py @@ -0,0 +1,47 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCKRDistributionAnalysis +from .mbcssm import EUCKR_SM_MODEL + + +class EUCKRProber(MultiByteCharSetProber): + def __init__(self): + super(EUCKRProber, self).__init__() + self.coding_sm = CodingStateMachine(EUCKR_SM_MODEL) + self.distribution_analyzer = EUCKRDistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "EUC-KR" + + @property + def language(self): + return "Korean" diff --git a/robot/lib/python3.8/site-packages/chardet/euctwfreq.py b/robot/lib/python3.8/site-packages/chardet/euctwfreq.py new file mode 100644 index 0000000000000000000000000000000000000000..ed7a995a3aa311583efa5a47e316d4490e5f3463 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/euctwfreq.py @@ -0,0 +1,387 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# EUCTW frequency table +# Converted from big5 work +# by Taiwan's Mandarin Promotion Council +# + +# 128 --> 0.42261 +# 256 --> 0.57851 +# 512 --> 0.74851 +# 1024 --> 0.89384 +# 2048 --> 0.97583 +# +# Idea Distribution Ratio = 0.74851/(1-0.74851) =2.98 +# Random Distribution Ration = 512/(5401-512)=0.105 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR + +EUCTW_TYPICAL_DISTRIBUTION_RATIO = 0.75 + +# Char to FreqOrder table , +EUCTW_TABLE_SIZE = 5376 + +EUCTW_CHAR_TO_FREQ_ORDER = ( + 1,1800,1506, 255,1431, 198, 9, 82, 6,7310, 177, 202,3615,1256,2808, 110, # 2742 +3735, 33,3241, 261, 76, 44,2113, 16,2931,2184,1176, 659,3868, 26,3404,2643, # 2758 +1198,3869,3313,4060, 410,2211, 302, 590, 361,1963, 8, 204, 58,4296,7311,1931, # 2774 + 63,7312,7313, 317,1614, 75, 222, 159,4061,2412,1480,7314,3500,3068, 224,2809, # 2790 +3616, 3, 10,3870,1471, 29,2774,1135,2852,1939, 873, 130,3242,1123, 312,7315, # 2806 +4297,2051, 507, 252, 682,7316, 142,1914, 124, 206,2932, 34,3501,3173, 64, 604, # 2822 +7317,2494,1976,1977, 155,1990, 645, 641,1606,7318,3405, 337, 72, 406,7319, 80, # 2838 + 630, 238,3174,1509, 263, 939,1092,2644, 756,1440,1094,3406, 449, 69,2969, 591, # 2854 + 179,2095, 471, 115,2034,1843, 60, 50,2970, 134, 806,1868, 734,2035,3407, 180, # 2870 + 995,1607, 156, 537,2893, 688,7320, 319,1305, 779,2144, 514,2374, 298,4298, 359, # 2886 +2495, 90,2707,1338, 663, 11, 906,1099,2545, 20,2436, 182, 532,1716,7321, 732, # 2902 +1376,4062,1311,1420,3175, 25,2312,1056, 113, 399, 382,1949, 242,3408,2467, 529, # 2918 +3243, 475,1447,3617,7322, 117, 21, 656, 810,1297,2295,2329,3502,7323, 126,4063, # 2934 + 706, 456, 150, 613,4299, 71,1118,2036,4064, 145,3069, 85, 835, 486,2114,1246, # 2950 +1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,7324,2127,2354, 347,3736, 221, # 2966 +3503,3110,7325,1955,1153,4065, 83, 296,1199,3070, 192, 624, 93,7326, 822,1897, # 2982 +2810,3111, 795,2064, 991,1554,1542,1592, 27, 43,2853, 859, 139,1456, 860,4300, # 2998 + 437, 712,3871, 164,2392,3112, 695, 211,3017,2096, 195,3872,1608,3504,3505,3618, # 3014 +3873, 234, 811,2971,2097,3874,2229,1441,3506,1615,2375, 668,2076,1638, 305, 228, # 3030 +1664,4301, 467, 415,7327, 262,2098,1593, 239, 108, 300, 200,1033, 512,1247,2077, # 3046 +7328,7329,2173,3176,3619,2673, 593, 845,1062,3244, 88,1723,2037,3875,1950, 212, # 3062 + 266, 152, 149, 468,1898,4066,4302, 77, 187,7330,3018, 37, 5,2972,7331,3876, # 3078 +7332,7333, 39,2517,4303,2894,3177,2078, 55, 148, 74,4304, 545, 483,1474,1029, # 3094 +1665, 217,1869,1531,3113,1104,2645,4067, 24, 172,3507, 900,3877,3508,3509,4305, # 3110 + 32,1408,2811,1312, 329, 487,2355,2247,2708, 784,2674, 4,3019,3314,1427,1788, # 3126 + 188, 109, 499,7334,3620,1717,1789, 888,1217,3020,4306,7335,3510,7336,3315,1520, # 3142 +3621,3878, 196,1034, 775,7337,7338, 929,1815, 249, 439, 38,7339,1063,7340, 794, # 3158 +3879,1435,2296, 46, 178,3245,2065,7341,2376,7342, 214,1709,4307, 804, 35, 707, # 3174 + 324,3622,1601,2546, 140, 459,4068,7343,7344,1365, 839, 272, 978,2257,2572,3409, # 3190 +2128,1363,3623,1423, 697, 100,3071, 48, 70,1231, 495,3114,2193,7345,1294,7346, # 3206 +2079, 462, 586,1042,3246, 853, 256, 988, 185,2377,3410,1698, 434,1084,7347,3411, # 3222 + 314,2615,2775,4308,2330,2331, 569,2280, 637,1816,2518, 757,1162,1878,1616,3412, # 3238 + 287,1577,2115, 768,4309,1671,2854,3511,2519,1321,3737, 909,2413,7348,4069, 933, # 3254 +3738,7349,2052,2356,1222,4310, 765,2414,1322, 786,4311,7350,1919,1462,1677,2895, # 3270 +1699,7351,4312,1424,2437,3115,3624,2590,3316,1774,1940,3413,3880,4070, 309,1369, # 3286 +1130,2812, 364,2230,1653,1299,3881,3512,3882,3883,2646, 525,1085,3021, 902,2000, # 3302 +1475, 964,4313, 421,1844,1415,1057,2281, 940,1364,3116, 376,4314,4315,1381, 7, # 3318 +2520, 983,2378, 336,1710,2675,1845, 321,3414, 559,1131,3022,2742,1808,1132,1313, # 3334 + 265,1481,1857,7352, 352,1203,2813,3247, 167,1089, 420,2814, 776, 792,1724,3513, # 3350 +4071,2438,3248,7353,4072,7354, 446, 229, 333,2743, 901,3739,1200,1557,4316,2647, # 3366 +1920, 395,2744,2676,3740,4073,1835, 125, 916,3178,2616,4317,7355,7356,3741,7357, # 3382 +7358,7359,4318,3117,3625,1133,2547,1757,3415,1510,2313,1409,3514,7360,2145, 438, # 3398 +2591,2896,2379,3317,1068, 958,3023, 461, 311,2855,2677,4074,1915,3179,4075,1978, # 3414 + 383, 750,2745,2617,4076, 274, 539, 385,1278,1442,7361,1154,1964, 384, 561, 210, # 3430 + 98,1295,2548,3515,7362,1711,2415,1482,3416,3884,2897,1257, 129,7363,3742, 642, # 3446 + 523,2776,2777,2648,7364, 141,2231,1333, 68, 176, 441, 876, 907,4077, 603,2592, # 3462 + 710, 171,3417, 404, 549, 18,3118,2393,1410,3626,1666,7365,3516,4319,2898,4320, # 3478 +7366,2973, 368,7367, 146, 366, 99, 871,3627,1543, 748, 807,1586,1185, 22,2258, # 3494 + 379,3743,3180,7368,3181, 505,1941,2618,1991,1382,2314,7369, 380,2357, 218, 702, # 3510 +1817,1248,3418,3024,3517,3318,3249,7370,2974,3628, 930,3250,3744,7371, 59,7372, # 3526 + 585, 601,4078, 497,3419,1112,1314,4321,1801,7373,1223,1472,2174,7374, 749,1836, # 3542 + 690,1899,3745,1772,3885,1476, 429,1043,1790,2232,2116, 917,4079, 447,1086,1629, # 3558 +7375, 556,7376,7377,2020,1654, 844,1090, 105, 550, 966,1758,2815,1008,1782, 686, # 3574 +1095,7378,2282, 793,1602,7379,3518,2593,4322,4080,2933,2297,4323,3746, 980,2496, # 3590 + 544, 353, 527,4324, 908,2678,2899,7380, 381,2619,1942,1348,7381,1341,1252, 560, # 3606 +3072,7382,3420,2856,7383,2053, 973, 886,2080, 143,4325,7384,7385, 157,3886, 496, # 3622 +4081, 57, 840, 540,2038,4326,4327,3421,2117,1445, 970,2259,1748,1965,2081,4082, # 3638 +3119,1234,1775,3251,2816,3629, 773,1206,2129,1066,2039,1326,3887,1738,1725,4083, # 3654 + 279,3120, 51,1544,2594, 423,1578,2130,2066, 173,4328,1879,7386,7387,1583, 264, # 3670 + 610,3630,4329,2439, 280, 154,7388,7389,7390,1739, 338,1282,3073, 693,2857,1411, # 3686 +1074,3747,2440,7391,4330,7392,7393,1240, 952,2394,7394,2900,1538,2679, 685,1483, # 3702 +4084,2468,1436, 953,4085,2054,4331, 671,2395, 79,4086,2441,3252, 608, 567,2680, # 3718 +3422,4087,4088,1691, 393,1261,1791,2396,7395,4332,7396,7397,7398,7399,1383,1672, # 3734 +3748,3182,1464, 522,1119, 661,1150, 216, 675,4333,3888,1432,3519, 609,4334,2681, # 3750 +2397,7400,7401,7402,4089,3025, 0,7403,2469, 315, 231,2442, 301,3319,4335,2380, # 3766 +7404, 233,4090,3631,1818,4336,4337,7405, 96,1776,1315,2082,7406, 257,7407,1809, # 3782 +3632,2709,1139,1819,4091,2021,1124,2163,2778,1777,2649,7408,3074, 363,1655,3183, # 3798 +7409,2975,7410,7411,7412,3889,1567,3890, 718, 103,3184, 849,1443, 341,3320,2934, # 3814 +1484,7413,1712, 127, 67, 339,4092,2398, 679,1412, 821,7414,7415, 834, 738, 351, # 3830 +2976,2146, 846, 235,1497,1880, 418,1992,3749,2710, 186,1100,2147,2746,3520,1545, # 3846 +1355,2935,2858,1377, 583,3891,4093,2573,2977,7416,1298,3633,1078,2549,3634,2358, # 3862 + 78,3750,3751, 267,1289,2099,2001,1594,4094, 348, 369,1274,2194,2175,1837,4338, # 3878 +1820,2817,3635,2747,2283,2002,4339,2936,2748, 144,3321, 882,4340,3892,2749,3423, # 3894 +4341,2901,7417,4095,1726, 320,7418,3893,3026, 788,2978,7419,2818,1773,1327,2859, # 3910 +3894,2819,7420,1306,4342,2003,1700,3752,3521,2359,2650, 787,2022, 506, 824,3636, # 3926 + 534, 323,4343,1044,3322,2023,1900, 946,3424,7421,1778,1500,1678,7422,1881,4344, # 3942 + 165, 243,4345,3637,2521, 123, 683,4096, 764,4346, 36,3895,1792, 589,2902, 816, # 3958 + 626,1667,3027,2233,1639,1555,1622,3753,3896,7423,3897,2860,1370,1228,1932, 891, # 3974 +2083,2903, 304,4097,7424, 292,2979,2711,3522, 691,2100,4098,1115,4347, 118, 662, # 3990 +7425, 611,1156, 854,2381,1316,2861, 2, 386, 515,2904,7426,7427,3253, 868,2234, # 4006 +1486, 855,2651, 785,2212,3028,7428,1040,3185,3523,7429,3121, 448,7430,1525,7431, # 4022 +2164,4348,7432,3754,7433,4099,2820,3524,3122, 503, 818,3898,3123,1568, 814, 676, # 4038 +1444, 306,1749,7434,3755,1416,1030, 197,1428, 805,2821,1501,4349,7435,7436,7437, # 4054 +1993,7438,4350,7439,7440,2195, 13,2779,3638,2980,3124,1229,1916,7441,3756,2131, # 4070 +7442,4100,4351,2399,3525,7443,2213,1511,1727,1120,7444,7445, 646,3757,2443, 307, # 4086 +7446,7447,1595,3186,7448,7449,7450,3639,1113,1356,3899,1465,2522,2523,7451, 519, # 4102 +7452, 128,2132, 92,2284,1979,7453,3900,1512, 342,3125,2196,7454,2780,2214,1980, # 4118 +3323,7455, 290,1656,1317, 789, 827,2360,7456,3758,4352, 562, 581,3901,7457, 401, # 4134 +4353,2248, 94,4354,1399,2781,7458,1463,2024,4355,3187,1943,7459, 828,1105,4101, # 4150 +1262,1394,7460,4102, 605,4356,7461,1783,2862,7462,2822, 819,2101, 578,2197,2937, # 4166 +7463,1502, 436,3254,4103,3255,2823,3902,2905,3425,3426,7464,2712,2315,7465,7466, # 4182 +2332,2067, 23,4357, 193, 826,3759,2102, 699,1630,4104,3075, 390,1793,1064,3526, # 4198 +7467,1579,3076,3077,1400,7468,4105,1838,1640,2863,7469,4358,4359, 137,4106, 598, # 4214 +3078,1966, 780, 104, 974,2938,7470, 278, 899, 253, 402, 572, 504, 493,1339,7471, # 4230 +3903,1275,4360,2574,2550,7472,3640,3029,3079,2249, 565,1334,2713, 863, 41,7473, # 4246 +7474,4361,7475,1657,2333, 19, 463,2750,4107, 606,7476,2981,3256,1087,2084,1323, # 4262 +2652,2982,7477,1631,1623,1750,4108,2682,7478,2864, 791,2714,2653,2334, 232,2416, # 4278 +7479,2983,1498,7480,2654,2620, 755,1366,3641,3257,3126,2025,1609, 119,1917,3427, # 4294 + 862,1026,4109,7481,3904,3760,4362,3905,4363,2260,1951,2470,7482,1125, 817,4110, # 4310 +4111,3906,1513,1766,2040,1487,4112,3030,3258,2824,3761,3127,7483,7484,1507,7485, # 4326 +2683, 733, 40,1632,1106,2865, 345,4113, 841,2524, 230,4364,2984,1846,3259,3428, # 4342 +7486,1263, 986,3429,7487, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562,3907, # 4358 +3908,2939, 967,2751,2655,1349, 592,2133,1692,3324,2985,1994,4114,1679,3909,1901, # 4374 +2185,7488, 739,3642,2715,1296,1290,7489,4115,2198,2199,1921,1563,2595,2551,1870, # 4390 +2752,2986,7490, 435,7491, 343,1108, 596, 17,1751,4365,2235,3430,3643,7492,4366, # 4406 + 294,3527,2940,1693, 477, 979, 281,2041,3528, 643,2042,3644,2621,2782,2261,1031, # 4422 +2335,2134,2298,3529,4367, 367,1249,2552,7493,3530,7494,4368,1283,3325,2004, 240, # 4438 +1762,3326,4369,4370, 836,1069,3128, 474,7495,2148,2525, 268,3531,7496,3188,1521, # 4454 +1284,7497,1658,1546,4116,7498,3532,3533,7499,4117,3327,2684,1685,4118, 961,1673, # 4470 +2622, 190,2005,2200,3762,4371,4372,7500, 570,2497,3645,1490,7501,4373,2623,3260, # 4486 +1956,4374, 584,1514, 396,1045,1944,7502,4375,1967,2444,7503,7504,4376,3910, 619, # 4502 +7505,3129,3261, 215,2006,2783,2553,3189,4377,3190,4378, 763,4119,3763,4379,7506, # 4518 +7507,1957,1767,2941,3328,3646,1174, 452,1477,4380,3329,3130,7508,2825,1253,2382, # 4534 +2186,1091,2285,4120, 492,7509, 638,1169,1824,2135,1752,3911, 648, 926,1021,1324, # 4550 +4381, 520,4382, 997, 847,1007, 892,4383,3764,2262,1871,3647,7510,2400,1784,4384, # 4566 +1952,2942,3080,3191,1728,4121,2043,3648,4385,2007,1701,3131,1551, 30,2263,4122, # 4582 +7511,2026,4386,3534,7512, 501,7513,4123, 594,3431,2165,1821,3535,3432,3536,3192, # 4598 + 829,2826,4124,7514,1680,3132,1225,4125,7515,3262,4387,4126,3133,2336,7516,4388, # 4614 +4127,7517,3912,3913,7518,1847,2383,2596,3330,7519,4389, 374,3914, 652,4128,4129, # 4630 + 375,1140, 798,7520,7521,7522,2361,4390,2264, 546,1659, 138,3031,2445,4391,7523, # 4646 +2250, 612,1848, 910, 796,3765,1740,1371, 825,3766,3767,7524,2906,2554,7525, 692, # 4662 + 444,3032,2624, 801,4392,4130,7526,1491, 244,1053,3033,4131,4132, 340,7527,3915, # 4678 +1041,2987, 293,1168, 87,1357,7528,1539, 959,7529,2236, 721, 694,4133,3768, 219, # 4694 +1478, 644,1417,3331,2656,1413,1401,1335,1389,3916,7530,7531,2988,2362,3134,1825, # 4710 + 730,1515, 184,2827, 66,4393,7532,1660,2943, 246,3332, 378,1457, 226,3433, 975, # 4726 +3917,2944,1264,3537, 674, 696,7533, 163,7534,1141,2417,2166, 713,3538,3333,4394, # 4742 +3918,7535,7536,1186, 15,7537,1079,1070,7538,1522,3193,3539, 276,1050,2716, 758, # 4758 +1126, 653,2945,3263,7539,2337, 889,3540,3919,3081,2989, 903,1250,4395,3920,3434, # 4774 +3541,1342,1681,1718, 766,3264, 286, 89,2946,3649,7540,1713,7541,2597,3334,2990, # 4790 +7542,2947,2215,3194,2866,7543,4396,2498,2526, 181, 387,1075,3921, 731,2187,3335, # 4806 +7544,3265, 310, 313,3435,2299, 770,4134, 54,3034, 189,4397,3082,3769,3922,7545, # 4822 +1230,1617,1849, 355,3542,4135,4398,3336, 111,4136,3650,1350,3135,3436,3035,4137, # 4838 +2149,3266,3543,7546,2784,3923,3924,2991, 722,2008,7547,1071, 247,1207,2338,2471, # 4854 +1378,4399,2009, 864,1437,1214,4400, 373,3770,1142,2216, 667,4401, 442,2753,2555, # 4870 +3771,3925,1968,4138,3267,1839, 837, 170,1107, 934,1336,1882,7548,7549,2118,4139, # 4886 +2828, 743,1569,7550,4402,4140, 582,2384,1418,3437,7551,1802,7552, 357,1395,1729, # 4902 +3651,3268,2418,1564,2237,7553,3083,3772,1633,4403,1114,2085,4141,1532,7554, 482, # 4918 +2446,4404,7555,7556,1492, 833,1466,7557,2717,3544,1641,2829,7558,1526,1272,3652, # 4934 +4142,1686,1794, 416,2556,1902,1953,1803,7559,3773,2785,3774,1159,2316,7560,2867, # 4950 +4405,1610,1584,3036,2419,2754, 443,3269,1163,3136,7561,7562,3926,7563,4143,2499, # 4966 +3037,4406,3927,3137,2103,1647,3545,2010,1872,4144,7564,4145, 431,3438,7565, 250, # 4982 + 97, 81,4146,7566,1648,1850,1558, 160, 848,7567, 866, 740,1694,7568,2201,2830, # 4998 +3195,4147,4407,3653,1687, 950,2472, 426, 469,3196,3654,3655,3928,7569,7570,1188, # 5014 + 424,1995, 861,3546,4148,3775,2202,2685, 168,1235,3547,4149,7571,2086,1674,4408, # 5030 +3337,3270, 220,2557,1009,7572,3776, 670,2992, 332,1208, 717,7573,7574,3548,2447, # 5046 +3929,3338,7575, 513,7576,1209,2868,3339,3138,4409,1080,7577,7578,7579,7580,2527, # 5062 +3656,3549, 815,1587,3930,3931,7581,3550,3439,3777,1254,4410,1328,3038,1390,3932, # 5078 +1741,3933,3778,3934,7582, 236,3779,2448,3271,7583,7584,3657,3780,1273,3781,4411, # 5094 +7585, 308,7586,4412, 245,4413,1851,2473,1307,2575, 430, 715,2136,2449,7587, 270, # 5110 + 199,2869,3935,7588,3551,2718,1753, 761,1754, 725,1661,1840,4414,3440,3658,7589, # 5126 +7590, 587, 14,3272, 227,2598, 326, 480,2265, 943,2755,3552, 291, 650,1883,7591, # 5142 +1702,1226, 102,1547, 62,3441, 904,4415,3442,1164,4150,7592,7593,1224,1548,2756, # 5158 + 391, 498,1493,7594,1386,1419,7595,2055,1177,4416, 813, 880,1081,2363, 566,1145, # 5174 +4417,2286,1001,1035,2558,2599,2238, 394,1286,7596,7597,2068,7598, 86,1494,1730, # 5190 +3936, 491,1588, 745, 897,2948, 843,3340,3937,2757,2870,3273,1768, 998,2217,2069, # 5206 + 397,1826,1195,1969,3659,2993,3341, 284,7599,3782,2500,2137,2119,1903,7600,3938, # 5222 +2150,3939,4151,1036,3443,1904, 114,2559,4152, 209,1527,7601,7602,2949,2831,2625, # 5238 +2385,2719,3139, 812,2560,7603,3274,7604,1559, 737,1884,3660,1210, 885, 28,2686, # 5254 +3553,3783,7605,4153,1004,1779,4418,7606, 346,1981,2218,2687,4419,3784,1742, 797, # 5270 +1642,3940,1933,1072,1384,2151, 896,3941,3275,3661,3197,2871,3554,7607,2561,1958, # 5286 +4420,2450,1785,7608,7609,7610,3942,4154,1005,1308,3662,4155,2720,4421,4422,1528, # 5302 +2600, 161,1178,4156,1982, 987,4423,1101,4157, 631,3943,1157,3198,2420,1343,1241, # 5318 +1016,2239,2562, 372, 877,2339,2501,1160, 555,1934, 911,3944,7611, 466,1170, 169, # 5334 +1051,2907,2688,3663,2474,2994,1182,2011,2563,1251,2626,7612, 992,2340,3444,1540, # 5350 +2721,1201,2070,2401,1996,2475,7613,4424, 528,1922,2188,1503,1873,1570,2364,3342, # 5366 +3276,7614, 557,1073,7615,1827,3445,2087,2266,3140,3039,3084, 767,3085,2786,4425, # 5382 +1006,4158,4426,2341,1267,2176,3664,3199, 778,3945,3200,2722,1597,2657,7616,4427, # 5398 +7617,3446,7618,7619,7620,3277,2689,1433,3278, 131, 95,1504,3946, 723,4159,3141, # 5414 +1841,3555,2758,2189,3947,2027,2104,3665,7621,2995,3948,1218,7622,3343,3201,3949, # 5430 +4160,2576, 248,1634,3785, 912,7623,2832,3666,3040,3786, 654, 53,7624,2996,7625, # 5446 +1688,4428, 777,3447,1032,3950,1425,7626, 191, 820,2120,2833, 971,4429, 931,3202, # 5462 + 135, 664, 783,3787,1997, 772,2908,1935,3951,3788,4430,2909,3203, 282,2723, 640, # 5478 +1372,3448,1127, 922, 325,3344,7627,7628, 711,2044,7629,7630,3952,2219,2787,1936, # 5494 +3953,3345,2220,2251,3789,2300,7631,4431,3790,1258,3279,3954,3204,2138,2950,3955, # 5510 +3956,7632,2221, 258,3205,4432, 101,1227,7633,3280,1755,7634,1391,3281,7635,2910, # 5526 +2056, 893,7636,7637,7638,1402,4161,2342,7639,7640,3206,3556,7641,7642, 878,1325, # 5542 +1780,2788,4433, 259,1385,2577, 744,1183,2267,4434,7643,3957,2502,7644, 684,1024, # 5558 +4162,7645, 472,3557,3449,1165,3282,3958,3959, 322,2152, 881, 455,1695,1152,1340, # 5574 + 660, 554,2153,4435,1058,4436,4163, 830,1065,3346,3960,4437,1923,7646,1703,1918, # 5590 +7647, 932,2268, 122,7648,4438, 947, 677,7649,3791,2627, 297,1905,1924,2269,4439, # 5606 +2317,3283,7650,7651,4164,7652,4165, 84,4166, 112, 989,7653, 547,1059,3961, 701, # 5622 +3558,1019,7654,4167,7655,3450, 942, 639, 457,2301,2451, 993,2951, 407, 851, 494, # 5638 +4440,3347, 927,7656,1237,7657,2421,3348, 573,4168, 680, 921,2911,1279,1874, 285, # 5654 + 790,1448,1983, 719,2167,7658,7659,4441,3962,3963,1649,7660,1541, 563,7661,1077, # 5670 +7662,3349,3041,3451, 511,2997,3964,3965,3667,3966,1268,2564,3350,3207,4442,4443, # 5686 +7663, 535,1048,1276,1189,2912,2028,3142,1438,1373,2834,2952,1134,2012,7664,4169, # 5702 +1238,2578,3086,1259,7665, 700,7666,2953,3143,3668,4170,7667,4171,1146,1875,1906, # 5718 +4444,2601,3967, 781,2422, 132,1589, 203, 147, 273,2789,2402, 898,1786,2154,3968, # 5734 +3969,7668,3792,2790,7669,7670,4445,4446,7671,3208,7672,1635,3793, 965,7673,1804, # 5750 +2690,1516,3559,1121,1082,1329,3284,3970,1449,3794, 65,1128,2835,2913,2759,1590, # 5766 +3795,7674,7675, 12,2658, 45, 976,2579,3144,4447, 517,2528,1013,1037,3209,7676, # 5782 +3796,2836,7677,3797,7678,3452,7679,2602, 614,1998,2318,3798,3087,2724,2628,7680, # 5798 +2580,4172, 599,1269,7681,1810,3669,7682,2691,3088, 759,1060, 489,1805,3351,3285, # 5814 +1358,7683,7684,2386,1387,1215,2629,2252, 490,7685,7686,4173,1759,2387,2343,7687, # 5830 +4448,3799,1907,3971,2630,1806,3210,4449,3453,3286,2760,2344, 874,7688,7689,3454, # 5846 +3670,1858, 91,2914,3671,3042,3800,4450,7690,3145,3972,2659,7691,3455,1202,1403, # 5862 +3801,2954,2529,1517,2503,4451,3456,2504,7692,4452,7693,2692,1885,1495,1731,3973, # 5878 +2365,4453,7694,2029,7695,7696,3974,2693,1216, 237,2581,4174,2319,3975,3802,4454, # 5894 +4455,2694,3560,3457, 445,4456,7697,7698,7699,7700,2761, 61,3976,3672,1822,3977, # 5910 +7701, 687,2045, 935, 925, 405,2660, 703,1096,1859,2725,4457,3978,1876,1367,2695, # 5926 +3352, 918,2105,1781,2476, 334,3287,1611,1093,4458, 564,3146,3458,3673,3353, 945, # 5942 +2631,2057,4459,7702,1925, 872,4175,7703,3459,2696,3089, 349,4176,3674,3979,4460, # 5958 +3803,4177,3675,2155,3980,4461,4462,4178,4463,2403,2046, 782,3981, 400, 251,4179, # 5974 +1624,7704,7705, 277,3676, 299,1265, 476,1191,3804,2121,4180,4181,1109, 205,7706, # 5990 +2582,1000,2156,3561,1860,7707,7708,7709,4464,7710,4465,2565, 107,2477,2157,3982, # 6006 +3460,3147,7711,1533, 541,1301, 158, 753,4182,2872,3562,7712,1696, 370,1088,4183, # 6022 +4466,3563, 579, 327, 440, 162,2240, 269,1937,1374,3461, 968,3043, 56,1396,3090, # 6038 +2106,3288,3354,7713,1926,2158,4467,2998,7714,3564,7715,7716,3677,4468,2478,7717, # 6054 +2791,7718,1650,4469,7719,2603,7720,7721,3983,2661,3355,1149,3356,3984,3805,3985, # 6070 +7722,1076, 49,7723, 951,3211,3289,3290, 450,2837, 920,7724,1811,2792,2366,4184, # 6086 +1908,1138,2367,3806,3462,7725,3212,4470,1909,1147,1518,2423,4471,3807,7726,4472, # 6102 +2388,2604, 260,1795,3213,7727,7728,3808,3291, 708,7729,3565,1704,7730,3566,1351, # 6118 +1618,3357,2999,1886, 944,4185,3358,4186,3044,3359,4187,7731,3678, 422, 413,1714, # 6134 +3292, 500,2058,2345,4188,2479,7732,1344,1910, 954,7733,1668,7734,7735,3986,2404, # 6150 +4189,3567,3809,4190,7736,2302,1318,2505,3091, 133,3092,2873,4473, 629, 31,2838, # 6166 +2697,3810,4474, 850, 949,4475,3987,2955,1732,2088,4191,1496,1852,7737,3988, 620, # 6182 +3214, 981,1242,3679,3360,1619,3680,1643,3293,2139,2452,1970,1719,3463,2168,7738, # 6198 +3215,7739,7740,3361,1828,7741,1277,4476,1565,2047,7742,1636,3568,3093,7743, 869, # 6214 +2839, 655,3811,3812,3094,3989,3000,3813,1310,3569,4477,7744,7745,7746,1733, 558, # 6230 +4478,3681, 335,1549,3045,1756,4192,3682,1945,3464,1829,1291,1192, 470,2726,2107, # 6246 +2793, 913,1054,3990,7747,1027,7748,3046,3991,4479, 982,2662,3362,3148,3465,3216, # 6262 +3217,1946,2794,7749, 571,4480,7750,1830,7751,3570,2583,1523,2424,7752,2089, 984, # 6278 +4481,3683,1959,7753,3684, 852, 923,2795,3466,3685, 969,1519, 999,2048,2320,1705, # 6294 +7754,3095, 615,1662, 151, 597,3992,2405,2321,1049, 275,4482,3686,4193, 568,3687, # 6310 +3571,2480,4194,3688,7755,2425,2270, 409,3218,7756,1566,2874,3467,1002, 769,2840, # 6326 + 194,2090,3149,3689,2222,3294,4195, 628,1505,7757,7758,1763,2177,3001,3993, 521, # 6342 +1161,2584,1787,2203,2406,4483,3994,1625,4196,4197, 412, 42,3096, 464,7759,2632, # 6358 +4484,3363,1760,1571,2875,3468,2530,1219,2204,3814,2633,2140,2368,4485,4486,3295, # 6374 +1651,3364,3572,7760,7761,3573,2481,3469,7762,3690,7763,7764,2271,2091, 460,7765, # 6390 +4487,7766,3002, 962, 588,3574, 289,3219,2634,1116, 52,7767,3047,1796,7768,7769, # 6406 +7770,1467,7771,1598,1143,3691,4198,1984,1734,1067,4488,1280,3365, 465,4489,1572, # 6422 + 510,7772,1927,2241,1812,1644,3575,7773,4490,3692,7774,7775,2663,1573,1534,7776, # 6438 +7777,4199, 536,1807,1761,3470,3815,3150,2635,7778,7779,7780,4491,3471,2915,1911, # 6454 +2796,7781,3296,1122, 377,3220,7782, 360,7783,7784,4200,1529, 551,7785,2059,3693, # 6470 +1769,2426,7786,2916,4201,3297,3097,2322,2108,2030,4492,1404, 136,1468,1479, 672, # 6486 +1171,3221,2303, 271,3151,7787,2762,7788,2049, 678,2727, 865,1947,4493,7789,2013, # 6502 +3995,2956,7790,2728,2223,1397,3048,3694,4494,4495,1735,2917,3366,3576,7791,3816, # 6518 + 509,2841,2453,2876,3817,7792,7793,3152,3153,4496,4202,2531,4497,2304,1166,1010, # 6534 + 552, 681,1887,7794,7795,2957,2958,3996,1287,1596,1861,3154, 358, 453, 736, 175, # 6550 + 478,1117, 905,1167,1097,7796,1853,1530,7797,1706,7798,2178,3472,2287,3695,3473, # 6566 +3577,4203,2092,4204,7799,3367,1193,2482,4205,1458,2190,2205,1862,1888,1421,3298, # 6582 +2918,3049,2179,3474, 595,2122,7800,3997,7801,7802,4206,1707,2636, 223,3696,1359, # 6598 + 751,3098, 183,3475,7803,2797,3003, 419,2369, 633, 704,3818,2389, 241,7804,7805, # 6614 +7806, 838,3004,3697,2272,2763,2454,3819,1938,2050,3998,1309,3099,2242,1181,7807, # 6630 +1136,2206,3820,2370,1446,4207,2305,4498,7808,7809,4208,1055,2605, 484,3698,7810, # 6646 +3999, 625,4209,2273,3368,1499,4210,4000,7811,4001,4211,3222,2274,2275,3476,7812, # 6662 +7813,2764, 808,2606,3699,3369,4002,4212,3100,2532, 526,3370,3821,4213, 955,7814, # 6678 +1620,4214,2637,2427,7815,1429,3700,1669,1831, 994, 928,7816,3578,1260,7817,7818, # 6694 +7819,1948,2288, 741,2919,1626,4215,2729,2455, 867,1184, 362,3371,1392,7820,7821, # 6710 +4003,4216,1770,1736,3223,2920,4499,4500,1928,2698,1459,1158,7822,3050,3372,2877, # 6726 +1292,1929,2506,2842,3701,1985,1187,2071,2014,2607,4217,7823,2566,2507,2169,3702, # 6742 +2483,3299,7824,3703,4501,7825,7826, 666,1003,3005,1022,3579,4218,7827,4502,1813, # 6758 +2253, 574,3822,1603, 295,1535, 705,3823,4219, 283, 858, 417,7828,7829,3224,4503, # 6774 +4504,3051,1220,1889,1046,2276,2456,4004,1393,1599, 689,2567, 388,4220,7830,2484, # 6790 + 802,7831,2798,3824,2060,1405,2254,7832,4505,3825,2109,1052,1345,3225,1585,7833, # 6806 + 809,7834,7835,7836, 575,2730,3477, 956,1552,1469,1144,2323,7837,2324,1560,2457, # 6822 +3580,3226,4005, 616,2207,3155,2180,2289,7838,1832,7839,3478,4506,7840,1319,3704, # 6838 +3705,1211,3581,1023,3227,1293,2799,7841,7842,7843,3826, 607,2306,3827, 762,2878, # 6854 +1439,4221,1360,7844,1485,3052,7845,4507,1038,4222,1450,2061,2638,4223,1379,4508, # 6870 +2585,7846,7847,4224,1352,1414,2325,2921,1172,7848,7849,3828,3829,7850,1797,1451, # 6886 +7851,7852,7853,7854,2922,4006,4007,2485,2346, 411,4008,4009,3582,3300,3101,4509, # 6902 +1561,2664,1452,4010,1375,7855,7856, 47,2959, 316,7857,1406,1591,2923,3156,7858, # 6918 +1025,2141,3102,3157, 354,2731, 884,2224,4225,2407, 508,3706, 726,3583, 996,2428, # 6934 +3584, 729,7859, 392,2191,1453,4011,4510,3707,7860,7861,2458,3585,2608,1675,2800, # 6950 + 919,2347,2960,2348,1270,4511,4012, 73,7862,7863, 647,7864,3228,2843,2255,1550, # 6966 +1346,3006,7865,1332, 883,3479,7866,7867,7868,7869,3301,2765,7870,1212, 831,1347, # 6982 +4226,4512,2326,3830,1863,3053, 720,3831,4513,4514,3832,7871,4227,7872,7873,4515, # 6998 +7874,7875,1798,4516,3708,2609,4517,3586,1645,2371,7876,7877,2924, 669,2208,2665, # 7014 +2429,7878,2879,7879,7880,1028,3229,7881,4228,2408,7882,2256,1353,7883,7884,4518, # 7030 +3158, 518,7885,4013,7886,4229,1960,7887,2142,4230,7888,7889,3007,2349,2350,3833, # 7046 + 516,1833,1454,4014,2699,4231,4519,2225,2610,1971,1129,3587,7890,2766,7891,2961, # 7062 +1422, 577,1470,3008,1524,3373,7892,7893, 432,4232,3054,3480,7894,2586,1455,2508, # 7078 +2226,1972,1175,7895,1020,2732,4015,3481,4520,7896,2733,7897,1743,1361,3055,3482, # 7094 +2639,4016,4233,4521,2290, 895, 924,4234,2170, 331,2243,3056, 166,1627,3057,1098, # 7110 +7898,1232,2880,2227,3374,4522, 657, 403,1196,2372, 542,3709,3375,1600,4235,3483, # 7126 +7899,4523,2767,3230, 576, 530,1362,7900,4524,2533,2666,3710,4017,7901, 842,3834, # 7142 +7902,2801,2031,1014,4018, 213,2700,3376, 665, 621,4236,7903,3711,2925,2430,7904, # 7158 +2431,3302,3588,3377,7905,4237,2534,4238,4525,3589,1682,4239,3484,1380,7906, 724, # 7174 +2277, 600,1670,7907,1337,1233,4526,3103,2244,7908,1621,4527,7909, 651,4240,7910, # 7190 +1612,4241,2611,7911,2844,7912,2734,2307,3058,7913, 716,2459,3059, 174,1255,2701, # 7206 +4019,3590, 548,1320,1398, 728,4020,1574,7914,1890,1197,3060,4021,7915,3061,3062, # 7222 +3712,3591,3713, 747,7916, 635,4242,4528,7917,7918,7919,4243,7920,7921,4529,7922, # 7238 +3378,4530,2432, 451,7923,3714,2535,2072,4244,2735,4245,4022,7924,1764,4531,7925, # 7254 +4246, 350,7926,2278,2390,2486,7927,4247,4023,2245,1434,4024, 488,4532, 458,4248, # 7270 +4025,3715, 771,1330,2391,3835,2568,3159,2159,2409,1553,2667,3160,4249,7928,2487, # 7286 +2881,2612,1720,2702,4250,3379,4533,7929,2536,4251,7930,3231,4252,2768,7931,2015, # 7302 +2736,7932,1155,1017,3716,3836,7933,3303,2308, 201,1864,4253,1430,7934,4026,7935, # 7318 +7936,7937,7938,7939,4254,1604,7940, 414,1865, 371,2587,4534,4535,3485,2016,3104, # 7334 +4536,1708, 960,4255, 887, 389,2171,1536,1663,1721,7941,2228,4027,2351,2926,1580, # 7350 +7942,7943,7944,1744,7945,2537,4537,4538,7946,4539,7947,2073,7948,7949,3592,3380, # 7366 +2882,4256,7950,4257,2640,3381,2802, 673,2703,2460, 709,3486,4028,3593,4258,7951, # 7382 +1148, 502, 634,7952,7953,1204,4540,3594,1575,4541,2613,3717,7954,3718,3105, 948, # 7398 +3232, 121,1745,3837,1110,7955,4259,3063,2509,3009,4029,3719,1151,1771,3838,1488, # 7414 +4030,1986,7956,2433,3487,7957,7958,2093,7959,4260,3839,1213,1407,2803, 531,2737, # 7430 +2538,3233,1011,1537,7960,2769,4261,3106,1061,7961,3720,3721,1866,2883,7962,2017, # 7446 + 120,4262,4263,2062,3595,3234,2309,3840,2668,3382,1954,4542,7963,7964,3488,1047, # 7462 +2704,1266,7965,1368,4543,2845, 649,3383,3841,2539,2738,1102,2846,2669,7966,7967, # 7478 +1999,7968,1111,3596,2962,7969,2488,3842,3597,2804,1854,3384,3722,7970,7971,3385, # 7494 +2410,2884,3304,3235,3598,7972,2569,7973,3599,2805,4031,1460, 856,7974,3600,7975, # 7510 +2885,2963,7976,2886,3843,7977,4264, 632,2510, 875,3844,1697,3845,2291,7978,7979, # 7526 +4544,3010,1239, 580,4545,4265,7980, 914, 936,2074,1190,4032,1039,2123,7981,7982, # 7542 +7983,3386,1473,7984,1354,4266,3846,7985,2172,3064,4033, 915,3305,4267,4268,3306, # 7558 +1605,1834,7986,2739, 398,3601,4269,3847,4034, 328,1912,2847,4035,3848,1331,4270, # 7574 +3011, 937,4271,7987,3602,4036,4037,3387,2160,4546,3388, 524, 742, 538,3065,1012, # 7590 +7988,7989,3849,2461,7990, 658,1103, 225,3850,7991,7992,4547,7993,4548,7994,3236, # 7606 +1243,7995,4038, 963,2246,4549,7996,2705,3603,3161,7997,7998,2588,2327,7999,4550, # 7622 +8000,8001,8002,3489,3307, 957,3389,2540,2032,1930,2927,2462, 870,2018,3604,1746, # 7638 +2770,2771,2434,2463,8003,3851,8004,3723,3107,3724,3490,3390,3725,8005,1179,3066, # 7654 +8006,3162,2373,4272,3726,2541,3163,3108,2740,4039,8007,3391,1556,2542,2292, 977, # 7670 +2887,2033,4040,1205,3392,8008,1765,3393,3164,2124,1271,1689, 714,4551,3491,8009, # 7686 +2328,3852, 533,4273,3605,2181, 617,8010,2464,3308,3492,2310,8011,8012,3165,8013, # 7702 +8014,3853,1987, 618, 427,2641,3493,3394,8015,8016,1244,1690,8017,2806,4274,4552, # 7718 +8018,3494,8019,8020,2279,1576, 473,3606,4275,3395, 972,8021,3607,8022,3067,8023, # 7734 +8024,4553,4554,8025,3727,4041,4042,8026, 153,4555, 356,8027,1891,2888,4276,2143, # 7750 + 408, 803,2352,8028,3854,8029,4277,1646,2570,2511,4556,4557,3855,8030,3856,4278, # 7766 +8031,2411,3396, 752,8032,8033,1961,2964,8034, 746,3012,2465,8035,4279,3728, 698, # 7782 +4558,1892,4280,3608,2543,4559,3609,3857,8036,3166,3397,8037,1823,1302,4043,2706, # 7798 +3858,1973,4281,8038,4282,3167, 823,1303,1288,1236,2848,3495,4044,3398, 774,3859, # 7814 +8039,1581,4560,1304,2849,3860,4561,8040,2435,2161,1083,3237,4283,4045,4284, 344, # 7830 +1173, 288,2311, 454,1683,8041,8042,1461,4562,4046,2589,8043,8044,4563, 985, 894, # 7846 +8045,3399,3168,8046,1913,2928,3729,1988,8047,2110,1974,8048,4047,8049,2571,1194, # 7862 + 425,8050,4564,3169,1245,3730,4285,8051,8052,2850,8053, 636,4565,1855,3861, 760, # 7878 +1799,8054,4286,2209,1508,4566,4048,1893,1684,2293,8055,8056,8057,4287,4288,2210, # 7894 + 479,8058,8059, 832,8060,4049,2489,8061,2965,2490,3731, 990,3109, 627,1814,2642, # 7910 +4289,1582,4290,2125,2111,3496,4567,8062, 799,4291,3170,8063,4568,2112,1737,3013, # 7926 +1018, 543, 754,4292,3309,1676,4569,4570,4050,8064,1489,8065,3497,8066,2614,2889, # 7942 +4051,8067,8068,2966,8069,8070,8071,8072,3171,4571,4572,2182,1722,8073,3238,3239, # 7958 +1842,3610,1715, 481, 365,1975,1856,8074,8075,1962,2491,4573,8076,2126,3611,3240, # 7974 + 433,1894,2063,2075,8077, 602,2741,8078,8079,8080,8081,8082,3014,1628,3400,8083, # 7990 +3172,4574,4052,2890,4575,2512,8084,2544,2772,8085,8086,8087,3310,4576,2891,8088, # 8006 +4577,8089,2851,4578,4579,1221,2967,4053,2513,8090,8091,8092,1867,1989,8093,8094, # 8022 +8095,1895,8096,8097,4580,1896,4054, 318,8098,2094,4055,4293,8099,8100, 485,8101, # 8038 + 938,3862, 553,2670, 116,8102,3863,3612,8103,3498,2671,2773,3401,3311,2807,8104, # 8054 +3613,2929,4056,1747,2930,2968,8105,8106, 207,8107,8108,2672,4581,2514,8109,3015, # 8070 + 890,3614,3864,8110,1877,3732,3402,8111,2183,2353,3403,1652,8112,8113,8114, 941, # 8086 +2294, 208,3499,4057,2019, 330,4294,3865,2892,2492,3733,4295,8115,8116,8117,8118, # 8102 +) + diff --git a/robot/lib/python3.8/site-packages/chardet/euctwprober.py b/robot/lib/python3.8/site-packages/chardet/euctwprober.py new file mode 100644 index 0000000000000000000000000000000000000000..35669cc4dd809fa4007ee67c752f1be991135e77 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/euctwprober.py @@ -0,0 +1,46 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCTWDistributionAnalysis +from .mbcssm import EUCTW_SM_MODEL + +class EUCTWProber(MultiByteCharSetProber): + def __init__(self): + super(EUCTWProber, self).__init__() + self.coding_sm = CodingStateMachine(EUCTW_SM_MODEL) + self.distribution_analyzer = EUCTWDistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "EUC-TW" + + @property + def language(self): + return "Taiwan" diff --git a/robot/lib/python3.8/site-packages/chardet/gb2312freq.py b/robot/lib/python3.8/site-packages/chardet/gb2312freq.py new file mode 100644 index 0000000000000000000000000000000000000000..697837bd9a87a77fc468fd1f10dd470d01701362 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/gb2312freq.py @@ -0,0 +1,283 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# GB2312 most frequently used character table +# +# Char to FreqOrder table , from hz6763 + +# 512 --> 0.79 -- 0.79 +# 1024 --> 0.92 -- 0.13 +# 2048 --> 0.98 -- 0.06 +# 6768 --> 1.00 -- 0.02 +# +# Ideal Distribution Ratio = 0.79135/(1-0.79135) = 3.79 +# Random Distribution Ration = 512 / (3755 - 512) = 0.157 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher that RDR + +GB2312_TYPICAL_DISTRIBUTION_RATIO = 0.9 + +GB2312_TABLE_SIZE = 3760 + +GB2312_CHAR_TO_FREQ_ORDER = ( +1671, 749,1443,2364,3924,3807,2330,3921,1704,3463,2691,1511,1515, 572,3191,2205, +2361, 224,2558, 479,1711, 963,3162, 440,4060,1905,2966,2947,3580,2647,3961,3842, +2204, 869,4207, 970,2678,5626,2944,2956,1479,4048, 514,3595, 588,1346,2820,3409, + 249,4088,1746,1873,2047,1774, 581,1813, 358,1174,3590,1014,1561,4844,2245, 670, +1636,3112, 889,1286, 953, 556,2327,3060,1290,3141, 613, 185,3477,1367, 850,3820, +1715,2428,2642,2303,2732,3041,2562,2648,3566,3946,1349, 388,3098,2091,1360,3585, + 152,1687,1539, 738,1559, 59,1232,2925,2267,1388,1249,1741,1679,2960, 151,1566, +1125,1352,4271, 924,4296, 385,3166,4459, 310,1245,2850, 70,3285,2729,3534,3575, +2398,3298,3466,1960,2265, 217,3647, 864,1909,2084,4401,2773,1010,3269,5152, 853, +3051,3121,1244,4251,1895, 364,1499,1540,2313,1180,3655,2268, 562, 715,2417,3061, + 544, 336,3768,2380,1752,4075, 950, 280,2425,4382, 183,2759,3272, 333,4297,2155, +1688,2356,1444,1039,4540, 736,1177,3349,2443,2368,2144,2225, 565, 196,1482,3406, + 927,1335,4147, 692, 878,1311,1653,3911,3622,1378,4200,1840,2969,3149,2126,1816, +2534,1546,2393,2760, 737,2494, 13, 447, 245,2747, 38,2765,2129,2589,1079, 606, + 360, 471,3755,2890, 404, 848, 699,1785,1236, 370,2221,1023,3746,2074,2026,2023, +2388,1581,2119, 812,1141,3091,2536,1519, 804,2053, 406,1596,1090, 784, 548,4414, +1806,2264,2936,1100, 343,4114,5096, 622,3358, 743,3668,1510,1626,5020,3567,2513, +3195,4115,5627,2489,2991, 24,2065,2697,1087,2719, 48,1634, 315, 68, 985,2052, + 198,2239,1347,1107,1439, 597,2366,2172, 871,3307, 919,2487,2790,1867, 236,2570, +1413,3794, 906,3365,3381,1701,1982,1818,1524,2924,1205, 616,2586,2072,2004, 575, + 253,3099, 32,1365,1182, 197,1714,2454,1201, 554,3388,3224,2748, 756,2587, 250, +2567,1507,1517,3529,1922,2761,2337,3416,1961,1677,2452,2238,3153, 615, 911,1506, +1474,2495,1265,1906,2749,3756,3280,2161, 898,2714,1759,3450,2243,2444, 563, 26, +3286,2266,3769,3344,2707,3677, 611,1402, 531,1028,2871,4548,1375, 261,2948, 835, +1190,4134, 353, 840,2684,1900,3082,1435,2109,1207,1674, 329,1872,2781,4055,2686, +2104, 608,3318,2423,2957,2768,1108,3739,3512,3271,3985,2203,1771,3520,1418,2054, +1681,1153, 225,1627,2929, 162,2050,2511,3687,1954, 124,1859,2431,1684,3032,2894, + 585,4805,3969,2869,2704,2088,2032,2095,3656,2635,4362,2209, 256, 518,2042,2105, +3777,3657, 643,2298,1148,1779, 190, 989,3544, 414, 11,2135,2063,2979,1471, 403, +3678, 126, 770,1563, 671,2499,3216,2877, 600,1179, 307,2805,4937,1268,1297,2694, + 252,4032,1448,1494,1331,1394, 127,2256, 222,1647,1035,1481,3056,1915,1048, 873, +3651, 210, 33,1608,2516, 200,1520, 415, 102, 0,3389,1287, 817, 91,3299,2940, + 836,1814, 549,2197,1396,1669,2987,3582,2297,2848,4528,1070, 687, 20,1819, 121, +1552,1364,1461,1968,2617,3540,2824,2083, 177, 948,4938,2291, 110,4549,2066, 648, +3359,1755,2110,2114,4642,4845,1693,3937,3308,1257,1869,2123, 208,1804,3159,2992, +2531,2549,3361,2418,1350,2347,2800,2568,1291,2036,2680, 72, 842,1990, 212,1233, +1154,1586, 75,2027,3410,4900,1823,1337,2710,2676, 728,2810,1522,3026,4995, 157, + 755,1050,4022, 710, 785,1936,2194,2085,1406,2777,2400, 150,1250,4049,1206, 807, +1910, 534, 529,3309,1721,1660, 274, 39,2827, 661,2670,1578, 925,3248,3815,1094, +4278,4901,4252, 41,1150,3747,2572,2227,4501,3658,4902,3813,3357,3617,2884,2258, + 887, 538,4187,3199,1294,2439,3042,2329,2343,2497,1255, 107, 543,1527, 521,3478, +3568, 194,5062, 15, 961,3870,1241,1192,2664, 66,5215,3260,2111,1295,1127,2152, +3805,4135, 901,1164,1976, 398,1278, 530,1460, 748, 904,1054,1966,1426, 53,2909, + 509, 523,2279,1534, 536,1019, 239,1685, 460,2353, 673,1065,2401,3600,4298,2272, +1272,2363, 284,1753,3679,4064,1695, 81, 815,2677,2757,2731,1386, 859, 500,4221, +2190,2566, 757,1006,2519,2068,1166,1455, 337,2654,3203,1863,1682,1914,3025,1252, +1409,1366, 847, 714,2834,2038,3209, 964,2970,1901, 885,2553,1078,1756,3049, 301, +1572,3326, 688,2130,1996,2429,1805,1648,2930,3421,2750,3652,3088, 262,1158,1254, + 389,1641,1812, 526,1719, 923,2073,1073,1902, 468, 489,4625,1140, 857,2375,3070, +3319,2863, 380, 116,1328,2693,1161,2244, 273,1212,1884,2769,3011,1775,1142, 461, +3066,1200,2147,2212, 790, 702,2695,4222,1601,1058, 434,2338,5153,3640, 67,2360, +4099,2502, 618,3472,1329, 416,1132, 830,2782,1807,2653,3211,3510,1662, 192,2124, + 296,3979,1739,1611,3684, 23, 118, 324, 446,1239,1225, 293,2520,3814,3795,2535, +3116, 17,1074, 467,2692,2201, 387,2922, 45,1326,3055,1645,3659,2817, 958, 243, +1903,2320,1339,2825,1784,3289, 356, 576, 865,2315,2381,3377,3916,1088,3122,1713, +1655, 935, 628,4689,1034,1327, 441, 800, 720, 894,1979,2183,1528,5289,2702,1071, +4046,3572,2399,1571,3281, 79, 761,1103, 327, 134, 758,1899,1371,1615, 879, 442, + 215,2605,2579, 173,2048,2485,1057,2975,3317,1097,2253,3801,4263,1403,1650,2946, + 814,4968,3487,1548,2644,1567,1285, 2, 295,2636, 97, 946,3576, 832, 141,4257, +3273, 760,3821,3521,3156,2607, 949,1024,1733,1516,1803,1920,2125,2283,2665,3180, +1501,2064,3560,2171,1592, 803,3518,1416, 732,3897,4258,1363,1362,2458, 119,1427, + 602,1525,2608,1605,1639,3175, 694,3064, 10, 465, 76,2000,4846,4208, 444,3781, +1619,3353,2206,1273,3796, 740,2483, 320,1723,2377,3660,2619,1359,1137,1762,1724, +2345,2842,1850,1862, 912, 821,1866, 612,2625,1735,2573,3369,1093, 844, 89, 937, + 930,1424,3564,2413,2972,1004,3046,3019,2011, 711,3171,1452,4178, 428, 801,1943, + 432, 445,2811, 206,4136,1472, 730, 349, 73, 397,2802,2547, 998,1637,1167, 789, + 396,3217, 154,1218, 716,1120,1780,2819,4826,1931,3334,3762,2139,1215,2627, 552, +3664,3628,3232,1405,2383,3111,1356,2652,3577,3320,3101,1703, 640,1045,1370,1246, +4996, 371,1575,2436,1621,2210, 984,4033,1734,2638, 16,4529, 663,2755,3255,1451, +3917,2257,1253,1955,2234,1263,2951, 214,1229, 617, 485, 359,1831,1969, 473,2310, + 750,2058, 165, 80,2864,2419, 361,4344,2416,2479,1134, 796,3726,1266,2943, 860, +2715, 938, 390,2734,1313,1384, 248, 202, 877,1064,2854, 522,3907, 279,1602, 297, +2357, 395,3740, 137,2075, 944,4089,2584,1267,3802, 62,1533,2285, 178, 176, 780, +2440, 201,3707, 590, 478,1560,4354,2117,1075, 30, 74,4643,4004,1635,1441,2745, + 776,2596, 238,1077,1692,1912,2844, 605, 499,1742,3947, 241,3053, 980,1749, 936, +2640,4511,2582, 515,1543,2162,5322,2892,2993, 890,2148,1924, 665,1827,3581,1032, + 968,3163, 339,1044,1896, 270, 583,1791,1720,4367,1194,3488,3669, 43,2523,1657, + 163,2167, 290,1209,1622,3378, 550, 634,2508,2510, 695,2634,2384,2512,1476,1414, + 220,1469,2341,2138,2852,3183,2900,4939,2865,3502,1211,3680, 854,3227,1299,2976, +3172, 186,2998,1459, 443,1067,3251,1495, 321,1932,3054, 909, 753,1410,1828, 436, +2441,1119,1587,3164,2186,1258, 227, 231,1425,1890,3200,3942, 247, 959, 725,5254, +2741, 577,2158,2079, 929, 120, 174, 838,2813, 591,1115, 417,2024, 40,3240,1536, +1037, 291,4151,2354, 632,1298,2406,2500,3535,1825,1846,3451, 205,1171, 345,4238, + 18,1163, 811, 685,2208,1217, 425,1312,1508,1175,4308,2552,1033, 587,1381,3059, +2984,3482, 340,1316,4023,3972, 792,3176, 519, 777,4690, 918, 933,4130,2981,3741, + 90,3360,2911,2200,5184,4550, 609,3079,2030, 272,3379,2736, 363,3881,1130,1447, + 286, 779, 357,1169,3350,3137,1630,1220,2687,2391, 747,1277,3688,2618,2682,2601, +1156,3196,5290,4034,3102,1689,3596,3128, 874, 219,2783, 798, 508,1843,2461, 269, +1658,1776,1392,1913,2983,3287,2866,2159,2372, 829,4076, 46,4253,2873,1889,1894, + 915,1834,1631,2181,2318, 298, 664,2818,3555,2735, 954,3228,3117, 527,3511,2173, + 681,2712,3033,2247,2346,3467,1652, 155,2164,3382, 113,1994, 450, 899, 494, 994, +1237,2958,1875,2336,1926,3727, 545,1577,1550, 633,3473, 204,1305,3072,2410,1956, +2471, 707,2134, 841,2195,2196,2663,3843,1026,4940, 990,3252,4997, 368,1092, 437, +3212,3258,1933,1829, 675,2977,2893, 412, 943,3723,4644,3294,3283,2230,2373,5154, +2389,2241,2661,2323,1404,2524, 593, 787, 677,3008,1275,2059, 438,2709,2609,2240, +2269,2246,1446, 36,1568,1373,3892,1574,2301,1456,3962, 693,2276,5216,2035,1143, +2720,1919,1797,1811,2763,4137,2597,1830,1699,1488,1198,2090, 424,1694, 312,3634, +3390,4179,3335,2252,1214, 561,1059,3243,2295,2561, 975,5155,2321,2751,3772, 472, +1537,3282,3398,1047,2077,2348,2878,1323,3340,3076, 690,2906, 51, 369, 170,3541, +1060,2187,2688,3670,2541,1083,1683, 928,3918, 459, 109,4427, 599,3744,4286, 143, +2101,2730,2490, 82,1588,3036,2121, 281,1860, 477,4035,1238,2812,3020,2716,3312, +1530,2188,2055,1317, 843, 636,1808,1173,3495, 649, 181,1002, 147,3641,1159,2414, +3750,2289,2795, 813,3123,2610,1136,4368, 5,3391,4541,2174, 420, 429,1728, 754, +1228,2115,2219, 347,2223,2733, 735,1518,3003,2355,3134,1764,3948,3329,1888,2424, +1001,1234,1972,3321,3363,1672,1021,1450,1584, 226, 765, 655,2526,3404,3244,2302, +3665, 731, 594,2184, 319,1576, 621, 658,2656,4299,2099,3864,1279,2071,2598,2739, + 795,3086,3699,3908,1707,2352,2402,1382,3136,2475,1465,4847,3496,3865,1085,3004, +2591,1084, 213,2287,1963,3565,2250, 822, 793,4574,3187,1772,1789,3050, 595,1484, +1959,2770,1080,2650, 456, 422,2996, 940,3322,4328,4345,3092,2742, 965,2784, 739, +4124, 952,1358,2498,2949,2565, 332,2698,2378, 660,2260,2473,4194,3856,2919, 535, +1260,2651,1208,1428,1300,1949,1303,2942, 433,2455,2450,1251,1946, 614,1269, 641, +1306,1810,2737,3078,2912, 564,2365,1419,1415,1497,4460,2367,2185,1379,3005,1307, +3218,2175,1897,3063, 682,1157,4040,4005,1712,1160,1941,1399, 394, 402,2952,1573, +1151,2986,2404, 862, 299,2033,1489,3006, 346, 171,2886,3401,1726,2932, 168,2533, + 47,2507,1030,3735,1145,3370,1395,1318,1579,3609,4560,2857,4116,1457,2529,1965, + 504,1036,2690,2988,2405, 745,5871, 849,2397,2056,3081, 863,2359,3857,2096, 99, +1397,1769,2300,4428,1643,3455,1978,1757,3718,1440, 35,4879,3742,1296,4228,2280, + 160,5063,1599,2013, 166, 520,3479,1646,3345,3012, 490,1937,1545,1264,2182,2505, +1096,1188,1369,1436,2421,1667,2792,2460,1270,2122, 727,3167,2143, 806,1706,1012, +1800,3037, 960,2218,1882, 805, 139,2456,1139,1521, 851,1052,3093,3089, 342,2039, + 744,5097,1468,1502,1585,2087, 223, 939, 326,2140,2577, 892,2481,1623,4077, 982, +3708, 135,2131, 87,2503,3114,2326,1106, 876,1616, 547,2997,2831,2093,3441,4530, +4314, 9,3256,4229,4148, 659,1462,1986,1710,2046,2913,2231,4090,4880,5255,3392, +3274,1368,3689,4645,1477, 705,3384,3635,1068,1529,2941,1458,3782,1509, 100,1656, +2548, 718,2339, 408,1590,2780,3548,1838,4117,3719,1345,3530, 717,3442,2778,3220, +2898,1892,4590,3614,3371,2043,1998,1224,3483, 891, 635, 584,2559,3355, 733,1766, +1729,1172,3789,1891,2307, 781,2982,2271,1957,1580,5773,2633,2005,4195,3097,1535, +3213,1189,1934,5693,3262, 586,3118,1324,1598, 517,1564,2217,1868,1893,4445,3728, +2703,3139,1526,1787,1992,3882,2875,1549,1199,1056,2224,1904,2711,5098,4287, 338, +1993,3129,3489,2689,1809,2815,1997, 957,1855,3898,2550,3275,3057,1105,1319, 627, +1505,1911,1883,3526, 698,3629,3456,1833,1431, 746, 77,1261,2017,2296,1977,1885, + 125,1334,1600, 525,1798,1109,2222,1470,1945, 559,2236,1186,3443,2476,1929,1411, +2411,3135,1777,3372,2621,1841,1613,3229, 668,1430,1839,2643,2916, 195,1989,2671, +2358,1387, 629,3205,2293,5256,4439, 123,1310, 888,1879,4300,3021,3605,1003,1162, +3192,2910,2010, 140,2395,2859, 55,1082,2012,2901, 662, 419,2081,1438, 680,2774, +4654,3912,1620,1731,1625,5035,4065,2328, 512,1344, 802,5443,2163,2311,2537, 524, +3399, 98,1155,2103,1918,2606,3925,2816,1393,2465,1504,3773,2177,3963,1478,4346, + 180,1113,4655,3461,2028,1698, 833,2696,1235,1322,1594,4408,3623,3013,3225,2040, +3022, 541,2881, 607,3632,2029,1665,1219, 639,1385,1686,1099,2803,3231,1938,3188, +2858, 427, 676,2772,1168,2025, 454,3253,2486,3556, 230,1950, 580, 791,1991,1280, +1086,1974,2034, 630, 257,3338,2788,4903,1017, 86,4790, 966,2789,1995,1696,1131, + 259,3095,4188,1308, 179,1463,5257, 289,4107,1248, 42,3413,1725,2288, 896,1947, + 774,4474,4254, 604,3430,4264, 392,2514,2588, 452, 237,1408,3018, 988,4531,1970, +3034,3310, 540,2370,1562,1288,2990, 502,4765,1147, 4,1853,2708, 207, 294,2814, +4078,2902,2509, 684, 34,3105,3532,2551, 644, 709,2801,2344, 573,1727,3573,3557, +2021,1081,3100,4315,2100,3681, 199,2263,1837,2385, 146,3484,1195,2776,3949, 997, +1939,3973,1008,1091,1202,1962,1847,1149,4209,5444,1076, 493, 117,5400,2521, 972, +1490,2934,1796,4542,2374,1512,2933,2657, 413,2888,1135,2762,2314,2156,1355,2369, + 766,2007,2527,2170,3124,2491,2593,2632,4757,2437, 234,3125,3591,1898,1750,1376, +1942,3468,3138, 570,2127,2145,3276,4131, 962, 132,1445,4196, 19, 941,3624,3480, +3366,1973,1374,4461,3431,2629, 283,2415,2275, 808,2887,3620,2112,2563,1353,3610, + 955,1089,3103,1053, 96, 88,4097, 823,3808,1583, 399, 292,4091,3313, 421,1128, + 642,4006, 903,2539,1877,2082, 596, 29,4066,1790, 722,2157, 130, 995,1569, 769, +1485, 464, 513,2213, 288,1923,1101,2453,4316, 133, 486,2445, 50, 625, 487,2207, + 57, 423, 481,2962, 159,3729,1558, 491, 303, 482, 501, 240,2837, 112,3648,2392, +1783, 362, 8,3433,3422, 610,2793,3277,1390,1284,1654, 21,3823, 734, 367, 623, + 193, 287, 374,1009,1483, 816, 476, 313,2255,2340,1262,2150,2899,1146,2581, 782, +2116,1659,2018,1880, 255,3586,3314,1110,2867,2137,2564, 986,2767,5185,2006, 650, + 158, 926, 762, 881,3157,2717,2362,3587, 306,3690,3245,1542,3077,2427,1691,2478, +2118,2985,3490,2438, 539,2305, 983, 129,1754, 355,4201,2386, 827,2923, 104,1773, +2838,2771, 411,2905,3919, 376, 767, 122,1114, 828,2422,1817,3506, 266,3460,1007, +1609,4998, 945,2612,4429,2274, 726,1247,1964,2914,2199,2070,4002,4108, 657,3323, +1422, 579, 455,2764,4737,1222,2895,1670, 824,1223,1487,2525, 558, 861,3080, 598, +2659,2515,1967, 752,2583,2376,2214,4180, 977, 704,2464,4999,2622,4109,1210,2961, + 819,1541, 142,2284, 44, 418, 457,1126,3730,4347,4626,1644,1876,3671,1864, 302, +1063,5694, 624, 723,1984,3745,1314,1676,2488,1610,1449,3558,3569,2166,2098, 409, +1011,2325,3704,2306, 818,1732,1383,1824,1844,3757, 999,2705,3497,1216,1423,2683, +2426,2954,2501,2726,2229,1475,2554,5064,1971,1794,1666,2014,1343, 783, 724, 191, +2434,1354,2220,5065,1763,2752,2472,4152, 131, 175,2885,3434, 92,1466,4920,2616, +3871,3872,3866, 128,1551,1632, 669,1854,3682,4691,4125,1230, 188,2973,3290,1302, +1213, 560,3266, 917, 763,3909,3249,1760, 868,1958, 764,1782,2097, 145,2277,3774, +4462, 64,1491,3062, 971,2132,3606,2442, 221,1226,1617, 218, 323,1185,3207,3147, + 571, 619,1473,1005,1744,2281, 449,1887,2396,3685, 275, 375,3816,1743,3844,3731, + 845,1983,2350,4210,1377, 773, 967,3499,3052,3743,2725,4007,1697,1022,3943,1464, +3264,2855,2722,1952,1029,2839,2467, 84,4383,2215, 820,1391,2015,2448,3672, 377, +1948,2168, 797,2545,3536,2578,2645, 94,2874,1678, 405,1259,3071, 771, 546,1315, + 470,1243,3083, 895,2468, 981, 969,2037, 846,4181, 653,1276,2928, 14,2594, 557, +3007,2474, 156, 902,1338,1740,2574, 537,2518, 973,2282,2216,2433,1928, 138,2903, +1293,2631,1612, 646,3457, 839,2935, 111, 496,2191,2847, 589,3186, 149,3994,2060, +4031,2641,4067,3145,1870, 37,3597,2136,1025,2051,3009,3383,3549,1121,1016,3261, +1301, 251,2446,2599,2153, 872,3246, 637, 334,3705, 831, 884, 921,3065,3140,4092, +2198,1944, 246,2964, 108,2045,1152,1921,2308,1031, 203,3173,4170,1907,3890, 810, +1401,2003,1690, 506, 647,1242,2828,1761,1649,3208,2249,1589,3709,2931,5156,1708, + 498, 666,2613, 834,3817,1231, 184,2851,1124, 883,3197,2261,3710,1765,1553,2658, +1178,2639,2351, 93,1193, 942,2538,2141,4402, 235,1821, 870,1591,2192,1709,1871, +3341,1618,4126,2595,2334, 603, 651, 69, 701, 268,2662,3411,2555,1380,1606, 503, + 448, 254,2371,2646, 574,1187,2309,1770, 322,2235,1292,1801, 305, 566,1133, 229, +2067,2057, 706, 167, 483,2002,2672,3295,1820,3561,3067, 316, 378,2746,3452,1112, + 136,1981, 507,1651,2917,1117, 285,4591, 182,2580,3522,1304, 335,3303,1835,2504, +1795,1792,2248, 674,1018,2106,2449,1857,2292,2845, 976,3047,1781,2600,2727,1389, +1281, 52,3152, 153, 265,3950, 672,3485,3951,4463, 430,1183, 365, 278,2169, 27, +1407,1336,2304, 209,1340,1730,2202,1852,2403,2883, 979,1737,1062, 631,2829,2542, +3876,2592, 825,2086,2226,3048,3625, 352,1417,3724, 542, 991, 431,1351,3938,1861, +2294, 826,1361,2927,3142,3503,1738, 463,2462,2723, 582,1916,1595,2808, 400,3845, +3891,2868,3621,2254, 58,2492,1123, 910,2160,2614,1372,1603,1196,1072,3385,1700, +3267,1980, 696, 480,2430, 920, 799,1570,2920,1951,2041,4047,2540,1321,4223,2469, +3562,2228,1271,2602, 401,2833,3351,2575,5157, 907,2312,1256, 410, 263,3507,1582, + 996, 678,1849,2316,1480, 908,3545,2237, 703,2322, 667,1826,2849,1531,2604,2999, +2407,3146,2151,2630,1786,3711, 469,3542, 497,3899,2409, 858, 837,4446,3393,1274, + 786, 620,1845,2001,3311, 484, 308,3367,1204,1815,3691,2332,1532,2557,1842,2020, +2724,1927,2333,4440, 567, 22,1673,2728,4475,1987,1858,1144,1597, 101,1832,3601, + 12, 974,3783,4391, 951,1412, 1,3720, 453,4608,4041, 528,1041,1027,3230,2628, +1129, 875,1051,3291,1203,2262,1069,2860,2799,2149,2615,3278, 144,1758,3040, 31, + 475,1680, 366,2685,3184, 311,1642,4008,2466,5036,1593,1493,2809, 216,1420,1668, + 233, 304,2128,3284, 232,1429,1768,1040,2008,3407,2740,2967,2543, 242,2133, 778, +1565,2022,2620, 505,2189,2756,1098,2273, 372,1614, 708, 553,2846,2094,2278, 169, +3626,2835,4161, 228,2674,3165, 809,1454,1309, 466,1705,1095, 900,3423, 880,2667, +3751,5258,2317,3109,2571,4317,2766,1503,1342, 866,4447,1118, 63,2076, 314,1881, +1348,1061, 172, 978,3515,1747, 532, 511,3970, 6, 601, 905,2699,3300,1751, 276, +1467,3725,2668, 65,4239,2544,2779,2556,1604, 578,2451,1802, 992,2331,2624,1320, +3446, 713,1513,1013, 103,2786,2447,1661, 886,1702, 916, 654,3574,2031,1556, 751, +2178,2821,2179,1498,1538,2176, 271, 914,2251,2080,1325, 638,1953,2937,3877,2432, +2754, 95,3265,1716, 260,1227,4083, 775, 106,1357,3254, 426,1607, 555,2480, 772, +1985, 244,2546, 474, 495,1046,2611,1851,2061, 71,2089,1675,2590, 742,3758,2843, +3222,1433, 267,2180,2576,2826,2233,2092,3913,2435, 956,1745,3075, 856,2113,1116, + 451, 3,1988,2896,1398, 993,2463,1878,2049,1341,2718,2721,2870,2108, 712,2904, +4363,2753,2324, 277,2872,2349,2649, 384, 987, 435, 691,3000, 922, 164,3939, 652, +1500,1184,4153,2482,3373,2165,4848,2335,3775,3508,3154,2806,2830,1554,2102,1664, +2530,1434,2408, 893,1547,2623,3447,2832,2242,2532,3169,2856,3223,2078, 49,3770, +3469, 462, 318, 656,2259,3250,3069, 679,1629,2758, 344,1138,1104,3120,1836,1283, +3115,2154,1437,4448, 934, 759,1999, 794,2862,1038, 533,2560,1722,2342, 855,2626, +1197,1663,4476,3127, 85,4240,2528, 25,1111,1181,3673, 407,3470,4561,2679,2713, + 768,1925,2841,3986,1544,1165, 932, 373,1240,2146,1930,2673, 721,4766, 354,4333, + 391,2963, 187, 61,3364,1442,1102, 330,1940,1767, 341,3809,4118, 393,2496,2062, +2211, 105, 331, 300, 439, 913,1332, 626, 379,3304,1557, 328, 689,3952, 309,1555, + 931, 317,2517,3027, 325, 569, 686,2107,3084, 60,1042,1333,2794, 264,3177,4014, +1628, 258,3712, 7,4464,1176,1043,1778, 683, 114,1975, 78,1492, 383,1886, 510, + 386, 645,5291,2891,2069,3305,4138,3867,2939,2603,2493,1935,1066,1848,3588,1015, +1282,1289,4609, 697,1453,3044,2666,3611,1856,2412, 54, 719,1330, 568,3778,2459, +1748, 788, 492, 551,1191,1000, 488,3394,3763, 282,1799, 348,2016,1523,3155,2390, +1049, 382,2019,1788,1170, 729,2968,3523, 897,3926,2785,2938,3292, 350,2319,3238, +1718,1717,2655,3453,3143,4465, 161,2889,2980,2009,1421, 56,1908,1640,2387,2232, +1917,1874,2477,4921, 148, 83,3438, 592,4245,2882,1822,1055, 741, 115,1496,1624, + 381,1638,4592,1020, 516,3214, 458, 947,4575,1432, 211,1514,2926,1865,2142, 189, + 852,1221,1400,1486, 882,2299,4036, 351, 28,1122, 700,6479,6480,6481,6482,6483, #last 512 +) + diff --git a/robot/lib/python3.8/site-packages/chardet/gb2312prober.py b/robot/lib/python3.8/site-packages/chardet/gb2312prober.py new file mode 100644 index 0000000000000000000000000000000000000000..8446d2dd959721cc86d4ae5a7699197454f3aa91 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/gb2312prober.py @@ -0,0 +1,46 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import GB2312DistributionAnalysis +from .mbcssm import GB2312_SM_MODEL + +class GB2312Prober(MultiByteCharSetProber): + def __init__(self): + super(GB2312Prober, self).__init__() + self.coding_sm = CodingStateMachine(GB2312_SM_MODEL) + self.distribution_analyzer = GB2312DistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "GB2312" + + @property + def language(self): + return "Chinese" diff --git a/robot/lib/python3.8/site-packages/chardet/hebrewprober.py b/robot/lib/python3.8/site-packages/chardet/hebrewprober.py new file mode 100644 index 0000000000000000000000000000000000000000..b0e1bf49268203d1f9d14cbe73753d95dc66c8a4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/hebrewprober.py @@ -0,0 +1,292 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Shy Shalom +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .enums import ProbingState + +# This prober doesn't actually recognize a language or a charset. +# It is a helper prober for the use of the Hebrew model probers + +### General ideas of the Hebrew charset recognition ### +# +# Four main charsets exist in Hebrew: +# "ISO-8859-8" - Visual Hebrew +# "windows-1255" - Logical Hebrew +# "ISO-8859-8-I" - Logical Hebrew +# "x-mac-hebrew" - ?? Logical Hebrew ?? +# +# Both "ISO" charsets use a completely identical set of code points, whereas +# "windows-1255" and "x-mac-hebrew" are two different proper supersets of +# these code points. windows-1255 defines additional characters in the range +# 0x80-0x9F as some misc punctuation marks as well as some Hebrew-specific +# diacritics and additional 'Yiddish' ligature letters in the range 0xc0-0xd6. +# x-mac-hebrew defines similar additional code points but with a different +# mapping. +# +# As far as an average Hebrew text with no diacritics is concerned, all four +# charsets are identical with respect to code points. Meaning that for the +# main Hebrew alphabet, all four map the same values to all 27 Hebrew letters +# (including final letters). +# +# The dominant difference between these charsets is their directionality. +# "Visual" directionality means that the text is ordered as if the renderer is +# not aware of a BIDI rendering algorithm. The renderer sees the text and +# draws it from left to right. The text itself when ordered naturally is read +# backwards. A buffer of Visual Hebrew generally looks like so: +# "[last word of first line spelled backwards] [whole line ordered backwards +# and spelled backwards] [first word of first line spelled backwards] +# [end of line] [last word of second line] ... etc' " +# adding punctuation marks, numbers and English text to visual text is +# naturally also "visual" and from left to right. +# +# "Logical" directionality means the text is ordered "naturally" according to +# the order it is read. It is the responsibility of the renderer to display +# the text from right to left. A BIDI algorithm is used to place general +# punctuation marks, numbers and English text in the text. +# +# Texts in x-mac-hebrew are almost impossible to find on the Internet. From +# what little evidence I could find, it seems that its general directionality +# is Logical. +# +# To sum up all of the above, the Hebrew probing mechanism knows about two +# charsets: +# Visual Hebrew - "ISO-8859-8" - backwards text - Words and sentences are +# backwards while line order is natural. For charset recognition purposes +# the line order is unimportant (In fact, for this implementation, even +# word order is unimportant). +# Logical Hebrew - "windows-1255" - normal, naturally ordered text. +# +# "ISO-8859-8-I" is a subset of windows-1255 and doesn't need to be +# specifically identified. +# "x-mac-hebrew" is also identified as windows-1255. A text in x-mac-hebrew +# that contain special punctuation marks or diacritics is displayed with +# some unconverted characters showing as question marks. This problem might +# be corrected using another model prober for x-mac-hebrew. Due to the fact +# that x-mac-hebrew texts are so rare, writing another model prober isn't +# worth the effort and performance hit. +# +#### The Prober #### +# +# The prober is divided between two SBCharSetProbers and a HebrewProber, +# all of which are managed, created, fed data, inquired and deleted by the +# SBCSGroupProber. The two SBCharSetProbers identify that the text is in +# fact some kind of Hebrew, Logical or Visual. The final decision about which +# one is it is made by the HebrewProber by combining final-letter scores +# with the scores of the two SBCharSetProbers to produce a final answer. +# +# The SBCSGroupProber is responsible for stripping the original text of HTML +# tags, English characters, numbers, low-ASCII punctuation characters, spaces +# and new lines. It reduces any sequence of such characters to a single space. +# The buffer fed to each prober in the SBCS group prober is pure text in +# high-ASCII. +# The two SBCharSetProbers (model probers) share the same language model: +# Win1255Model. +# The first SBCharSetProber uses the model normally as any other +# SBCharSetProber does, to recognize windows-1255, upon which this model was +# built. The second SBCharSetProber is told to make the pair-of-letter +# lookup in the language model backwards. This in practice exactly simulates +# a visual Hebrew model using the windows-1255 logical Hebrew model. +# +# The HebrewProber is not using any language model. All it does is look for +# final-letter evidence suggesting the text is either logical Hebrew or visual +# Hebrew. Disjointed from the model probers, the results of the HebrewProber +# alone are meaningless. HebrewProber always returns 0.00 as confidence +# since it never identifies a charset by itself. Instead, the pointer to the +# HebrewProber is passed to the model probers as a helper "Name Prober". +# When the Group prober receives a positive identification from any prober, +# it asks for the name of the charset identified. If the prober queried is a +# Hebrew model prober, the model prober forwards the call to the +# HebrewProber to make the final decision. In the HebrewProber, the +# decision is made according to the final-letters scores maintained and Both +# model probers scores. The answer is returned in the form of the name of the +# charset identified, either "windows-1255" or "ISO-8859-8". + +class HebrewProber(CharSetProber): + # windows-1255 / ISO-8859-8 code points of interest + FINAL_KAF = 0xea + NORMAL_KAF = 0xeb + FINAL_MEM = 0xed + NORMAL_MEM = 0xee + FINAL_NUN = 0xef + NORMAL_NUN = 0xf0 + FINAL_PE = 0xf3 + NORMAL_PE = 0xf4 + FINAL_TSADI = 0xf5 + NORMAL_TSADI = 0xf6 + + # Minimum Visual vs Logical final letter score difference. + # If the difference is below this, don't rely solely on the final letter score + # distance. + MIN_FINAL_CHAR_DISTANCE = 5 + + # Minimum Visual vs Logical model score difference. + # If the difference is below this, don't rely at all on the model score + # distance. + MIN_MODEL_DISTANCE = 0.01 + + VISUAL_HEBREW_NAME = "ISO-8859-8" + LOGICAL_HEBREW_NAME = "windows-1255" + + def __init__(self): + super(HebrewProber, self).__init__() + self._final_char_logical_score = None + self._final_char_visual_score = None + self._prev = None + self._before_prev = None + self._logical_prober = None + self._visual_prober = None + self.reset() + + def reset(self): + self._final_char_logical_score = 0 + self._final_char_visual_score = 0 + # The two last characters seen in the previous buffer, + # mPrev and mBeforePrev are initialized to space in order to simulate + # a word delimiter at the beginning of the data + self._prev = ' ' + self._before_prev = ' ' + # These probers are owned by the group prober. + + def set_model_probers(self, logicalProber, visualProber): + self._logical_prober = logicalProber + self._visual_prober = visualProber + + def is_final(self, c): + return c in [self.FINAL_KAF, self.FINAL_MEM, self.FINAL_NUN, + self.FINAL_PE, self.FINAL_TSADI] + + def is_non_final(self, c): + # The normal Tsadi is not a good Non-Final letter due to words like + # 'lechotet' (to chat) containing an apostrophe after the tsadi. This + # apostrophe is converted to a space in FilterWithoutEnglishLetters + # causing the Non-Final tsadi to appear at an end of a word even + # though this is not the case in the original text. + # The letters Pe and Kaf rarely display a related behavior of not being + # a good Non-Final letter. Words like 'Pop', 'Winamp' and 'Mubarak' + # for example legally end with a Non-Final Pe or Kaf. However, the + # benefit of these letters as Non-Final letters outweighs the damage + # since these words are quite rare. + return c in [self.NORMAL_KAF, self.NORMAL_MEM, + self.NORMAL_NUN, self.NORMAL_PE] + + def feed(self, byte_str): + # Final letter analysis for logical-visual decision. + # Look for evidence that the received buffer is either logical Hebrew + # or visual Hebrew. + # The following cases are checked: + # 1) A word longer than 1 letter, ending with a final letter. This is + # an indication that the text is laid out "naturally" since the + # final letter really appears at the end. +1 for logical score. + # 2) A word longer than 1 letter, ending with a Non-Final letter. In + # normal Hebrew, words ending with Kaf, Mem, Nun, Pe or Tsadi, + # should not end with the Non-Final form of that letter. Exceptions + # to this rule are mentioned above in isNonFinal(). This is an + # indication that the text is laid out backwards. +1 for visual + # score + # 3) A word longer than 1 letter, starting with a final letter. Final + # letters should not appear at the beginning of a word. This is an + # indication that the text is laid out backwards. +1 for visual + # score. + # + # The visual score and logical score are accumulated throughout the + # text and are finally checked against each other in GetCharSetName(). + # No checking for final letters in the middle of words is done since + # that case is not an indication for either Logical or Visual text. + # + # We automatically filter out all 7-bit characters (replace them with + # spaces) so the word boundary detection works properly. [MAP] + + if self.state == ProbingState.NOT_ME: + # Both model probers say it's not them. No reason to continue. + return ProbingState.NOT_ME + + byte_str = self.filter_high_byte_only(byte_str) + + for cur in byte_str: + if cur == ' ': + # We stand on a space - a word just ended + if self._before_prev != ' ': + # next-to-last char was not a space so self._prev is not a + # 1 letter word + if self.is_final(self._prev): + # case (1) [-2:not space][-1:final letter][cur:space] + self._final_char_logical_score += 1 + elif self.is_non_final(self._prev): + # case (2) [-2:not space][-1:Non-Final letter][ + # cur:space] + self._final_char_visual_score += 1 + else: + # Not standing on a space + if ((self._before_prev == ' ') and + (self.is_final(self._prev)) and (cur != ' ')): + # case (3) [-2:space][-1:final letter][cur:not space] + self._final_char_visual_score += 1 + self._before_prev = self._prev + self._prev = cur + + # Forever detecting, till the end or until both model probers return + # ProbingState.NOT_ME (handled above) + return ProbingState.DETECTING + + @property + def charset_name(self): + # Make the decision: is it Logical or Visual? + # If the final letter score distance is dominant enough, rely on it. + finalsub = self._final_char_logical_score - self._final_char_visual_score + if finalsub >= self.MIN_FINAL_CHAR_DISTANCE: + return self.LOGICAL_HEBREW_NAME + if finalsub <= -self.MIN_FINAL_CHAR_DISTANCE: + return self.VISUAL_HEBREW_NAME + + # It's not dominant enough, try to rely on the model scores instead. + modelsub = (self._logical_prober.get_confidence() + - self._visual_prober.get_confidence()) + if modelsub > self.MIN_MODEL_DISTANCE: + return self.LOGICAL_HEBREW_NAME + if modelsub < -self.MIN_MODEL_DISTANCE: + return self.VISUAL_HEBREW_NAME + + # Still no good, back to final letter distance, maybe it'll save the + # day. + if finalsub < 0.0: + return self.VISUAL_HEBREW_NAME + + # (finalsub > 0 - Logical) or (don't know what to do) default to + # Logical. + return self.LOGICAL_HEBREW_NAME + + @property + def language(self): + return 'Hebrew' + + @property + def state(self): + # Remain active as long as any of the model probers are active. + if (self._logical_prober.state == ProbingState.NOT_ME) and \ + (self._visual_prober.state == ProbingState.NOT_ME): + return ProbingState.NOT_ME + return ProbingState.DETECTING diff --git a/robot/lib/python3.8/site-packages/chardet/jisfreq.py b/robot/lib/python3.8/site-packages/chardet/jisfreq.py new file mode 100644 index 0000000000000000000000000000000000000000..83fc082b545106d02622de20f2083e8a7562f96c --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/jisfreq.py @@ -0,0 +1,325 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# Sampling from about 20M text materials include literature and computer technology +# +# Japanese frequency table, applied to both S-JIS and EUC-JP +# They are sorted in order. + +# 128 --> 0.77094 +# 256 --> 0.85710 +# 512 --> 0.92635 +# 1024 --> 0.97130 +# 2048 --> 0.99431 +# +# Ideal Distribution Ratio = 0.92635 / (1-0.92635) = 12.58 +# Random Distribution Ration = 512 / (2965+62+83+86-512) = 0.191 +# +# Typical Distribution Ratio, 25% of IDR + +JIS_TYPICAL_DISTRIBUTION_RATIO = 3.0 + +# Char to FreqOrder table , +JIS_TABLE_SIZE = 4368 + +JIS_CHAR_TO_FREQ_ORDER = ( + 40, 1, 6, 182, 152, 180, 295,2127, 285, 381,3295,4304,3068,4606,3165,3510, # 16 +3511,1822,2785,4607,1193,2226,5070,4608, 171,2996,1247, 18, 179,5071, 856,1661, # 32 +1262,5072, 619, 127,3431,3512,3230,1899,1700, 232, 228,1294,1298, 284, 283,2041, # 48 +2042,1061,1062, 48, 49, 44, 45, 433, 434,1040,1041, 996, 787,2997,1255,4305, # 64 +2108,4609,1684,1648,5073,5074,5075,5076,5077,5078,3687,5079,4610,5080,3927,3928, # 80 +5081,3296,3432, 290,2285,1471,2187,5082,2580,2825,1303,2140,1739,1445,2691,3375, # 96 +1691,3297,4306,4307,4611, 452,3376,1182,2713,3688,3069,4308,5083,5084,5085,5086, # 112 +5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102, # 128 +5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,4097,5113,5114,5115,5116,5117, # 144 +5118,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133, # 160 +5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148,5149, # 176 +5150,5151,5152,4612,5153,5154,5155,5156,5157,5158,5159,5160,5161,5162,5163,5164, # 192 +5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,1472, 598, 618, 820,1205, # 208 +1309,1412,1858,1307,1692,5176,5177,5178,5179,5180,5181,5182,1142,1452,1234,1172, # 224 +1875,2043,2149,1793,1382,2973, 925,2404,1067,1241, 960,1377,2935,1491, 919,1217, # 240 +1865,2030,1406,1499,2749,4098,5183,5184,5185,5186,5187,5188,2561,4099,3117,1804, # 256 +2049,3689,4309,3513,1663,5189,3166,3118,3298,1587,1561,3433,5190,3119,1625,2998, # 272 +3299,4613,1766,3690,2786,4614,5191,5192,5193,5194,2161, 26,3377, 2,3929, 20, # 288 +3691, 47,4100, 50, 17, 16, 35, 268, 27, 243, 42, 155, 24, 154, 29, 184, # 304 + 4, 91, 14, 92, 53, 396, 33, 289, 9, 37, 64, 620, 21, 39, 321, 5, # 320 + 12, 11, 52, 13, 3, 208, 138, 0, 7, 60, 526, 141, 151,1069, 181, 275, # 336 +1591, 83, 132,1475, 126, 331, 829, 15, 69, 160, 59, 22, 157, 55,1079, 312, # 352 + 109, 38, 23, 25, 10, 19, 79,5195, 61, 382,1124, 8, 30,5196,5197,5198, # 368 +5199,5200,5201,5202,5203,5204,5205,5206, 89, 62, 74, 34,2416, 112, 139, 196, # 384 + 271, 149, 84, 607, 131, 765, 46, 88, 153, 683, 76, 874, 101, 258, 57, 80, # 400 + 32, 364, 121,1508, 169,1547, 68, 235, 145,2999, 41, 360,3027, 70, 63, 31, # 416 + 43, 259, 262,1383, 99, 533, 194, 66, 93, 846, 217, 192, 56, 106, 58, 565, # 432 + 280, 272, 311, 256, 146, 82, 308, 71, 100, 128, 214, 655, 110, 261, 104,1140, # 448 + 54, 51, 36, 87, 67,3070, 185,2618,2936,2020, 28,1066,2390,2059,5207,5208, # 464 +5209,5210,5211,5212,5213,5214,5215,5216,4615,5217,5218,5219,5220,5221,5222,5223, # 480 +5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234,5235,5236,3514,5237,5238, # 496 +5239,5240,5241,5242,5243,5244,2297,2031,4616,4310,3692,5245,3071,5246,3598,5247, # 512 +4617,3231,3515,5248,4101,4311,4618,3808,4312,4102,5249,4103,4104,3599,5250,5251, # 528 +5252,5253,5254,5255,5256,5257,5258,5259,5260,5261,5262,5263,5264,5265,5266,5267, # 544 +5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278,5279,5280,5281,5282,5283, # 560 +5284,5285,5286,5287,5288,5289,5290,5291,5292,5293,5294,5295,5296,5297,5298,5299, # 576 +5300,5301,5302,5303,5304,5305,5306,5307,5308,5309,5310,5311,5312,5313,5314,5315, # 592 +5316,5317,5318,5319,5320,5321,5322,5323,5324,5325,5326,5327,5328,5329,5330,5331, # 608 +5332,5333,5334,5335,5336,5337,5338,5339,5340,5341,5342,5343,5344,5345,5346,5347, # 624 +5348,5349,5350,5351,5352,5353,5354,5355,5356,5357,5358,5359,5360,5361,5362,5363, # 640 +5364,5365,5366,5367,5368,5369,5370,5371,5372,5373,5374,5375,5376,5377,5378,5379, # 656 +5380,5381, 363, 642,2787,2878,2788,2789,2316,3232,2317,3434,2011, 165,1942,3930, # 672 +3931,3932,3933,5382,4619,5383,4620,5384,5385,5386,5387,5388,5389,5390,5391,5392, # 688 +5393,5394,5395,5396,5397,5398,5399,5400,5401,5402,5403,5404,5405,5406,5407,5408, # 704 +5409,5410,5411,5412,5413,5414,5415,5416,5417,5418,5419,5420,5421,5422,5423,5424, # 720 +5425,5426,5427,5428,5429,5430,5431,5432,5433,5434,5435,5436,5437,5438,5439,5440, # 736 +5441,5442,5443,5444,5445,5446,5447,5448,5449,5450,5451,5452,5453,5454,5455,5456, # 752 +5457,5458,5459,5460,5461,5462,5463,5464,5465,5466,5467,5468,5469,5470,5471,5472, # 768 +5473,5474,5475,5476,5477,5478,5479,5480,5481,5482,5483,5484,5485,5486,5487,5488, # 784 +5489,5490,5491,5492,5493,5494,5495,5496,5497,5498,5499,5500,5501,5502,5503,5504, # 800 +5505,5506,5507,5508,5509,5510,5511,5512,5513,5514,5515,5516,5517,5518,5519,5520, # 816 +5521,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536, # 832 +5537,5538,5539,5540,5541,5542,5543,5544,5545,5546,5547,5548,5549,5550,5551,5552, # 848 +5553,5554,5555,5556,5557,5558,5559,5560,5561,5562,5563,5564,5565,5566,5567,5568, # 864 +5569,5570,5571,5572,5573,5574,5575,5576,5577,5578,5579,5580,5581,5582,5583,5584, # 880 +5585,5586,5587,5588,5589,5590,5591,5592,5593,5594,5595,5596,5597,5598,5599,5600, # 896 +5601,5602,5603,5604,5605,5606,5607,5608,5609,5610,5611,5612,5613,5614,5615,5616, # 912 +5617,5618,5619,5620,5621,5622,5623,5624,5625,5626,5627,5628,5629,5630,5631,5632, # 928 +5633,5634,5635,5636,5637,5638,5639,5640,5641,5642,5643,5644,5645,5646,5647,5648, # 944 +5649,5650,5651,5652,5653,5654,5655,5656,5657,5658,5659,5660,5661,5662,5663,5664, # 960 +5665,5666,5667,5668,5669,5670,5671,5672,5673,5674,5675,5676,5677,5678,5679,5680, # 976 +5681,5682,5683,5684,5685,5686,5687,5688,5689,5690,5691,5692,5693,5694,5695,5696, # 992 +5697,5698,5699,5700,5701,5702,5703,5704,5705,5706,5707,5708,5709,5710,5711,5712, # 1008 +5713,5714,5715,5716,5717,5718,5719,5720,5721,5722,5723,5724,5725,5726,5727,5728, # 1024 +5729,5730,5731,5732,5733,5734,5735,5736,5737,5738,5739,5740,5741,5742,5743,5744, # 1040 +5745,5746,5747,5748,5749,5750,5751,5752,5753,5754,5755,5756,5757,5758,5759,5760, # 1056 +5761,5762,5763,5764,5765,5766,5767,5768,5769,5770,5771,5772,5773,5774,5775,5776, # 1072 +5777,5778,5779,5780,5781,5782,5783,5784,5785,5786,5787,5788,5789,5790,5791,5792, # 1088 +5793,5794,5795,5796,5797,5798,5799,5800,5801,5802,5803,5804,5805,5806,5807,5808, # 1104 +5809,5810,5811,5812,5813,5814,5815,5816,5817,5818,5819,5820,5821,5822,5823,5824, # 1120 +5825,5826,5827,5828,5829,5830,5831,5832,5833,5834,5835,5836,5837,5838,5839,5840, # 1136 +5841,5842,5843,5844,5845,5846,5847,5848,5849,5850,5851,5852,5853,5854,5855,5856, # 1152 +5857,5858,5859,5860,5861,5862,5863,5864,5865,5866,5867,5868,5869,5870,5871,5872, # 1168 +5873,5874,5875,5876,5877,5878,5879,5880,5881,5882,5883,5884,5885,5886,5887,5888, # 1184 +5889,5890,5891,5892,5893,5894,5895,5896,5897,5898,5899,5900,5901,5902,5903,5904, # 1200 +5905,5906,5907,5908,5909,5910,5911,5912,5913,5914,5915,5916,5917,5918,5919,5920, # 1216 +5921,5922,5923,5924,5925,5926,5927,5928,5929,5930,5931,5932,5933,5934,5935,5936, # 1232 +5937,5938,5939,5940,5941,5942,5943,5944,5945,5946,5947,5948,5949,5950,5951,5952, # 1248 +5953,5954,5955,5956,5957,5958,5959,5960,5961,5962,5963,5964,5965,5966,5967,5968, # 1264 +5969,5970,5971,5972,5973,5974,5975,5976,5977,5978,5979,5980,5981,5982,5983,5984, # 1280 +5985,5986,5987,5988,5989,5990,5991,5992,5993,5994,5995,5996,5997,5998,5999,6000, # 1296 +6001,6002,6003,6004,6005,6006,6007,6008,6009,6010,6011,6012,6013,6014,6015,6016, # 1312 +6017,6018,6019,6020,6021,6022,6023,6024,6025,6026,6027,6028,6029,6030,6031,6032, # 1328 +6033,6034,6035,6036,6037,6038,6039,6040,6041,6042,6043,6044,6045,6046,6047,6048, # 1344 +6049,6050,6051,6052,6053,6054,6055,6056,6057,6058,6059,6060,6061,6062,6063,6064, # 1360 +6065,6066,6067,6068,6069,6070,6071,6072,6073,6074,6075,6076,6077,6078,6079,6080, # 1376 +6081,6082,6083,6084,6085,6086,6087,6088,6089,6090,6091,6092,6093,6094,6095,6096, # 1392 +6097,6098,6099,6100,6101,6102,6103,6104,6105,6106,6107,6108,6109,6110,6111,6112, # 1408 +6113,6114,2044,2060,4621, 997,1235, 473,1186,4622, 920,3378,6115,6116, 379,1108, # 1424 +4313,2657,2735,3934,6117,3809, 636,3233, 573,1026,3693,3435,2974,3300,2298,4105, # 1440 + 854,2937,2463, 393,2581,2417, 539, 752,1280,2750,2480, 140,1161, 440, 708,1569, # 1456 + 665,2497,1746,1291,1523,3000, 164,1603, 847,1331, 537,1997, 486, 508,1693,2418, # 1472 +1970,2227, 878,1220, 299,1030, 969, 652,2751, 624,1137,3301,2619, 65,3302,2045, # 1488 +1761,1859,3120,1930,3694,3516, 663,1767, 852, 835,3695, 269, 767,2826,2339,1305, # 1504 + 896,1150, 770,1616,6118, 506,1502,2075,1012,2519, 775,2520,2975,2340,2938,4314, # 1520 +3028,2086,1224,1943,2286,6119,3072,4315,2240,1273,1987,3935,1557, 175, 597, 985, # 1536 +3517,2419,2521,1416,3029, 585, 938,1931,1007,1052,1932,1685,6120,3379,4316,4623, # 1552 + 804, 599,3121,1333,2128,2539,1159,1554,2032,3810, 687,2033,2904, 952, 675,1467, # 1568 +3436,6121,2241,1096,1786,2440,1543,1924, 980,1813,2228, 781,2692,1879, 728,1918, # 1584 +3696,4624, 548,1950,4625,1809,1088,1356,3303,2522,1944, 502, 972, 373, 513,2827, # 1600 + 586,2377,2391,1003,1976,1631,6122,2464,1084, 648,1776,4626,2141, 324, 962,2012, # 1616 +2177,2076,1384, 742,2178,1448,1173,1810, 222, 102, 301, 445, 125,2420, 662,2498, # 1632 + 277, 200,1476,1165,1068, 224,2562,1378,1446, 450,1880, 659, 791, 582,4627,2939, # 1648 +3936,1516,1274, 555,2099,3697,1020,1389,1526,3380,1762,1723,1787,2229, 412,2114, # 1664 +1900,2392,3518, 512,2597, 427,1925,2341,3122,1653,1686,2465,2499, 697, 330, 273, # 1680 + 380,2162, 951, 832, 780, 991,1301,3073, 965,2270,3519, 668,2523,2636,1286, 535, # 1696 +1407, 518, 671, 957,2658,2378, 267, 611,2197,3030,6123, 248,2299, 967,1799,2356, # 1712 + 850,1418,3437,1876,1256,1480,2828,1718,6124,6125,1755,1664,2405,6126,4628,2879, # 1728 +2829, 499,2179, 676,4629, 557,2329,2214,2090, 325,3234, 464, 811,3001, 992,2342, # 1744 +2481,1232,1469, 303,2242, 466,1070,2163, 603,1777,2091,4630,2752,4631,2714, 322, # 1760 +2659,1964,1768, 481,2188,1463,2330,2857,3600,2092,3031,2421,4632,2318,2070,1849, # 1776 +2598,4633,1302,2254,1668,1701,2422,3811,2905,3032,3123,2046,4106,1763,1694,4634, # 1792 +1604, 943,1724,1454, 917, 868,2215,1169,2940, 552,1145,1800,1228,1823,1955, 316, # 1808 +1080,2510, 361,1807,2830,4107,2660,3381,1346,1423,1134,4108,6127, 541,1263,1229, # 1824 +1148,2540, 545, 465,1833,2880,3438,1901,3074,2482, 816,3937, 713,1788,2500, 122, # 1840 +1575, 195,1451,2501,1111,6128, 859, 374,1225,2243,2483,4317, 390,1033,3439,3075, # 1856 +2524,1687, 266, 793,1440,2599, 946, 779, 802, 507, 897,1081, 528,2189,1292, 711, # 1872 +1866,1725,1167,1640, 753, 398,2661,1053, 246, 348,4318, 137,1024,3440,1600,2077, # 1888 +2129, 825,4319, 698, 238, 521, 187,2300,1157,2423,1641,1605,1464,1610,1097,2541, # 1904 +1260,1436, 759,2255,1814,2150, 705,3235, 409,2563,3304, 561,3033,2005,2564, 726, # 1920 +1956,2343,3698,4109, 949,3812,3813,3520,1669, 653,1379,2525, 881,2198, 632,2256, # 1936 +1027, 778,1074, 733,1957, 514,1481,2466, 554,2180, 702,3938,1606,1017,1398,6129, # 1952 +1380,3521, 921, 993,1313, 594, 449,1489,1617,1166, 768,1426,1360, 495,1794,3601, # 1968 +1177,3602,1170,4320,2344, 476, 425,3167,4635,3168,1424, 401,2662,1171,3382,1998, # 1984 +1089,4110, 477,3169, 474,6130,1909, 596,2831,1842, 494, 693,1051,1028,1207,3076, # 2000 + 606,2115, 727,2790,1473,1115, 743,3522, 630, 805,1532,4321,2021, 366,1057, 838, # 2016 + 684,1114,2142,4322,2050,1492,1892,1808,2271,3814,2424,1971,1447,1373,3305,1090, # 2032 +1536,3939,3523,3306,1455,2199, 336, 369,2331,1035, 584,2393, 902, 718,2600,6131, # 2048 +2753, 463,2151,1149,1611,2467, 715,1308,3124,1268, 343,1413,3236,1517,1347,2663, # 2064 +2093,3940,2022,1131,1553,2100,2941,1427,3441,2942,1323,2484,6132,1980, 872,2368, # 2080 +2441,2943, 320,2369,2116,1082, 679,1933,3941,2791,3815, 625,1143,2023, 422,2200, # 2096 +3816,6133, 730,1695, 356,2257,1626,2301,2858,2637,1627,1778, 937, 883,2906,2693, # 2112 +3002,1769,1086, 400,1063,1325,3307,2792,4111,3077, 456,2345,1046, 747,6134,1524, # 2128 + 884,1094,3383,1474,2164,1059, 974,1688,2181,2258,1047, 345,1665,1187, 358, 875, # 2144 +3170, 305, 660,3524,2190,1334,1135,3171,1540,1649,2542,1527, 927, 968,2793, 885, # 2160 +1972,1850, 482, 500,2638,1218,1109,1085,2543,1654,2034, 876, 78,2287,1482,1277, # 2176 + 861,1675,1083,1779, 724,2754, 454, 397,1132,1612,2332, 893, 672,1237, 257,2259, # 2192 +2370, 135,3384, 337,2244, 547, 352, 340, 709,2485,1400, 788,1138,2511, 540, 772, # 2208 +1682,2260,2272,2544,2013,1843,1902,4636,1999,1562,2288,4637,2201,1403,1533, 407, # 2224 + 576,3308,1254,2071, 978,3385, 170, 136,1201,3125,2664,3172,2394, 213, 912, 873, # 2240 +3603,1713,2202, 699,3604,3699, 813,3442, 493, 531,1054, 468,2907,1483, 304, 281, # 2256 +4112,1726,1252,2094, 339,2319,2130,2639, 756,1563,2944, 748, 571,2976,1588,2425, # 2272 +2715,1851,1460,2426,1528,1392,1973,3237, 288,3309, 685,3386, 296, 892,2716,2216, # 2288 +1570,2245, 722,1747,2217, 905,3238,1103,6135,1893,1441,1965, 251,1805,2371,3700, # 2304 +2601,1919,1078, 75,2182,1509,1592,1270,2640,4638,2152,6136,3310,3817, 524, 706, # 2320 +1075, 292,3818,1756,2602, 317, 98,3173,3605,3525,1844,2218,3819,2502, 814, 567, # 2336 + 385,2908,1534,6137, 534,1642,3239, 797,6138,1670,1529, 953,4323, 188,1071, 538, # 2352 + 178, 729,3240,2109,1226,1374,2000,2357,2977, 731,2468,1116,2014,2051,6139,1261, # 2368 +1593, 803,2859,2736,3443, 556, 682, 823,1541,6140,1369,2289,1706,2794, 845, 462, # 2384 +2603,2665,1361, 387, 162,2358,1740, 739,1770,1720,1304,1401,3241,1049, 627,1571, # 2400 +2427,3526,1877,3942,1852,1500, 431,1910,1503, 677, 297,2795, 286,1433,1038,1198, # 2416 +2290,1133,1596,4113,4639,2469,1510,1484,3943,6141,2442, 108, 712,4640,2372, 866, # 2432 +3701,2755,3242,1348, 834,1945,1408,3527,2395,3243,1811, 824, 994,1179,2110,1548, # 2448 +1453, 790,3003, 690,4324,4325,2832,2909,3820,1860,3821, 225,1748, 310, 346,1780, # 2464 +2470, 821,1993,2717,2796, 828, 877,3528,2860,2471,1702,2165,2910,2486,1789, 453, # 2480 + 359,2291,1676, 73,1164,1461,1127,3311, 421, 604, 314,1037, 589, 116,2487, 737, # 2496 + 837,1180, 111, 244, 735,6142,2261,1861,1362, 986, 523, 418, 581,2666,3822, 103, # 2512 + 855, 503,1414,1867,2488,1091, 657,1597, 979, 605,1316,4641,1021,2443,2078,2001, # 2528 +1209, 96, 587,2166,1032, 260,1072,2153, 173, 94, 226,3244, 819,2006,4642,4114, # 2544 +2203, 231,1744, 782, 97,2667, 786,3387, 887, 391, 442,2219,4326,1425,6143,2694, # 2560 + 633,1544,1202, 483,2015, 592,2052,1958,2472,1655, 419, 129,4327,3444,3312,1714, # 2576 +1257,3078,4328,1518,1098, 865,1310,1019,1885,1512,1734, 469,2444, 148, 773, 436, # 2592 +1815,1868,1128,1055,4329,1245,2756,3445,2154,1934,1039,4643, 579,1238, 932,2320, # 2608 + 353, 205, 801, 115,2428, 944,2321,1881, 399,2565,1211, 678, 766,3944, 335,2101, # 2624 +1459,1781,1402,3945,2737,2131,1010, 844, 981,1326,1013, 550,1816,1545,2620,1335, # 2640 +1008, 371,2881, 936,1419,1613,3529,1456,1395,2273,1834,2604,1317,2738,2503, 416, # 2656 +1643,4330, 806,1126, 229, 591,3946,1314,1981,1576,1837,1666, 347,1790, 977,3313, # 2672 + 764,2861,1853, 688,2429,1920,1462, 77, 595, 415,2002,3034, 798,1192,4115,6144, # 2688 +2978,4331,3035,2695,2582,2072,2566, 430,2430,1727, 842,1396,3947,3702, 613, 377, # 2704 + 278, 236,1417,3388,3314,3174, 757,1869, 107,3530,6145,1194, 623,2262, 207,1253, # 2720 +2167,3446,3948, 492,1117,1935, 536,1838,2757,1246,4332, 696,2095,2406,1393,1572, # 2736 +3175,1782, 583, 190, 253,1390,2230, 830,3126,3389, 934,3245,1703,1749,2979,1870, # 2752 +2545,1656,2204, 869,2346,4116,3176,1817, 496,1764,4644, 942,1504, 404,1903,1122, # 2768 +1580,3606,2945,1022, 515, 372,1735, 955,2431,3036,6146,2797,1110,2302,2798, 617, # 2784 +6147, 441, 762,1771,3447,3607,3608,1904, 840,3037, 86, 939,1385, 572,1370,2445, # 2800 +1336, 114,3703, 898, 294, 203,3315, 703,1583,2274, 429, 961,4333,1854,1951,3390, # 2816 +2373,3704,4334,1318,1381, 966,1911,2322,1006,1155, 309, 989, 458,2718,1795,1372, # 2832 +1203, 252,1689,1363,3177, 517,1936, 168,1490, 562, 193,3823,1042,4117,1835, 551, # 2848 + 470,4645, 395, 489,3448,1871,1465,2583,2641, 417,1493, 279,1295, 511,1236,1119, # 2864 + 72,1231,1982,1812,3004, 871,1564, 984,3449,1667,2696,2096,4646,2347,2833,1673, # 2880 +3609, 695,3246,2668, 807,1183,4647, 890, 388,2333,1801,1457,2911,1765,1477,1031, # 2896 +3316,3317,1278,3391,2799,2292,2526, 163,3450,4335,2669,1404,1802,6148,2323,2407, # 2912 +1584,1728,1494,1824,1269, 298, 909,3318,1034,1632, 375, 776,1683,2061, 291, 210, # 2928 +1123, 809,1249,1002,2642,3038, 206,1011,2132, 144, 975, 882,1565, 342, 667, 754, # 2944 +1442,2143,1299,2303,2062, 447, 626,2205,1221,2739,2912,1144,1214,2206,2584, 760, # 2960 +1715, 614, 950,1281,2670,2621, 810, 577,1287,2546,4648, 242,2168, 250,2643, 691, # 2976 + 123,2644, 647, 313,1029, 689,1357,2946,1650, 216, 771,1339,1306, 808,2063, 549, # 2992 + 913,1371,2913,2914,6149,1466,1092,1174,1196,1311,2605,2396,1783,1796,3079, 406, # 3008 +2671,2117,3949,4649, 487,1825,2220,6150,2915, 448,2348,1073,6151,2397,1707, 130, # 3024 + 900,1598, 329, 176,1959,2527,1620,6152,2275,4336,3319,1983,2191,3705,3610,2155, # 3040 +3706,1912,1513,1614,6153,1988, 646, 392,2304,1589,3320,3039,1826,1239,1352,1340, # 3056 +2916, 505,2567,1709,1437,2408,2547, 906,6154,2672, 384,1458,1594,1100,1329, 710, # 3072 + 423,3531,2064,2231,2622,1989,2673,1087,1882, 333, 841,3005,1296,2882,2379, 580, # 3088 +1937,1827,1293,2585, 601, 574, 249,1772,4118,2079,1120, 645, 901,1176,1690, 795, # 3104 +2207, 478,1434, 516,1190,1530, 761,2080, 930,1264, 355, 435,1552, 644,1791, 987, # 3120 + 220,1364,1163,1121,1538, 306,2169,1327,1222, 546,2645, 218, 241, 610,1704,3321, # 3136 +1984,1839,1966,2528, 451,6155,2586,3707,2568, 907,3178, 254,2947, 186,1845,4650, # 3152 + 745, 432,1757, 428,1633, 888,2246,2221,2489,3611,2118,1258,1265, 956,3127,1784, # 3168 +4337,2490, 319, 510, 119, 457,3612, 274,2035,2007,4651,1409,3128, 970,2758, 590, # 3184 +2800, 661,2247,4652,2008,3950,1420,1549,3080,3322,3951,1651,1375,2111, 485,2491, # 3200 +1429,1156,6156,2548,2183,1495, 831,1840,2529,2446, 501,1657, 307,1894,3247,1341, # 3216 + 666, 899,2156,1539,2549,1559, 886, 349,2208,3081,2305,1736,3824,2170,2759,1014, # 3232 +1913,1386, 542,1397,2948, 490, 368, 716, 362, 159, 282,2569,1129,1658,1288,1750, # 3248 +2674, 276, 649,2016, 751,1496, 658,1818,1284,1862,2209,2087,2512,3451, 622,2834, # 3264 + 376, 117,1060,2053,1208,1721,1101,1443, 247,1250,3179,1792,3952,2760,2398,3953, # 3280 +6157,2144,3708, 446,2432,1151,2570,3452,2447,2761,2835,1210,2448,3082, 424,2222, # 3296 +1251,2449,2119,2836, 504,1581,4338, 602, 817, 857,3825,2349,2306, 357,3826,1470, # 3312 +1883,2883, 255, 958, 929,2917,3248, 302,4653,1050,1271,1751,2307,1952,1430,2697, # 3328 +2719,2359, 354,3180, 777, 158,2036,4339,1659,4340,4654,2308,2949,2248,1146,2232, # 3344 +3532,2720,1696,2623,3827,6158,3129,1550,2698,1485,1297,1428, 637, 931,2721,2145, # 3360 + 914,2550,2587, 81,2450, 612, 827,2646,1242,4655,1118,2884, 472,1855,3181,3533, # 3376 +3534, 569,1353,2699,1244,1758,2588,4119,2009,2762,2171,3709,1312,1531,6159,1152, # 3392 +1938, 134,1830, 471,3710,2276,1112,1535,3323,3453,3535, 982,1337,2950, 488, 826, # 3408 + 674,1058,1628,4120,2017, 522,2399, 211, 568,1367,3454, 350, 293,1872,1139,3249, # 3424 +1399,1946,3006,1300,2360,3324, 588, 736,6160,2606, 744, 669,3536,3828,6161,1358, # 3440 + 199, 723, 848, 933, 851,1939,1505,1514,1338,1618,1831,4656,1634,3613, 443,2740, # 3456 +3829, 717,1947, 491,1914,6162,2551,1542,4121,1025,6163,1099,1223, 198,3040,2722, # 3472 + 370, 410,1905,2589, 998,1248,3182,2380, 519,1449,4122,1710, 947, 928,1153,4341, # 3488 +2277, 344,2624,1511, 615, 105, 161,1212,1076,1960,3130,2054,1926,1175,1906,2473, # 3504 + 414,1873,2801,6164,2309, 315,1319,3325, 318,2018,2146,2157, 963, 631, 223,4342, # 3520 +4343,2675, 479,3711,1197,2625,3712,2676,2361,6165,4344,4123,6166,2451,3183,1886, # 3536 +2184,1674,1330,1711,1635,1506, 799, 219,3250,3083,3954,1677,3713,3326,2081,3614, # 3552 +1652,2073,4657,1147,3041,1752, 643,1961, 147,1974,3955,6167,1716,2037, 918,3007, # 3568 +1994, 120,1537, 118, 609,3184,4345, 740,3455,1219, 332,1615,3830,6168,1621,2980, # 3584 +1582, 783, 212, 553,2350,3714,1349,2433,2082,4124, 889,6169,2310,1275,1410, 973, # 3600 + 166,1320,3456,1797,1215,3185,2885,1846,2590,2763,4658, 629, 822,3008, 763, 940, # 3616 +1990,2862, 439,2409,1566,1240,1622, 926,1282,1907,2764, 654,2210,1607, 327,1130, # 3632 +3956,1678,1623,6170,2434,2192, 686, 608,3831,3715, 903,3957,3042,6171,2741,1522, # 3648 +1915,1105,1555,2552,1359, 323,3251,4346,3457, 738,1354,2553,2311,2334,1828,2003, # 3664 +3832,1753,2351,1227,6172,1887,4125,1478,6173,2410,1874,1712,1847, 520,1204,2607, # 3680 + 264,4659, 836,2677,2102, 600,4660,3833,2278,3084,6174,4347,3615,1342, 640, 532, # 3696 + 543,2608,1888,2400,2591,1009,4348,1497, 341,1737,3616,2723,1394, 529,3252,1321, # 3712 + 983,4661,1515,2120, 971,2592, 924, 287,1662,3186,4349,2700,4350,1519, 908,1948, # 3728 +2452, 156, 796,1629,1486,2223,2055, 694,4126,1259,1036,3392,1213,2249,2742,1889, # 3744 +1230,3958,1015, 910, 408, 559,3617,4662, 746, 725, 935,4663,3959,3009,1289, 563, # 3760 + 867,4664,3960,1567,2981,2038,2626, 988,2263,2381,4351, 143,2374, 704,1895,6175, # 3776 +1188,3716,2088, 673,3085,2362,4352, 484,1608,1921,2765,2918, 215, 904,3618,3537, # 3792 + 894, 509, 976,3043,2701,3961,4353,2837,2982, 498,6176,6177,1102,3538,1332,3393, # 3808 +1487,1636,1637, 233, 245,3962, 383, 650, 995,3044, 460,1520,1206,2352, 749,3327, # 3824 + 530, 700, 389,1438,1560,1773,3963,2264, 719,2951,2724,3834, 870,1832,1644,1000, # 3840 + 839,2474,3717, 197,1630,3394, 365,2886,3964,1285,2133, 734, 922, 818,1106, 732, # 3856 + 480,2083,1774,3458, 923,2279,1350, 221,3086, 85,2233,2234,3835,1585,3010,2147, # 3872 +1387,1705,2382,1619,2475, 133, 239,2802,1991,1016,2084,2383, 411,2838,1113, 651, # 3888 +1985,1160,3328, 990,1863,3087,1048,1276,2647, 265,2627,1599,3253,2056, 150, 638, # 3904 +2019, 656, 853, 326,1479, 680,1439,4354,1001,1759, 413,3459,3395,2492,1431, 459, # 3920 +4355,1125,3329,2265,1953,1450,2065,2863, 849, 351,2678,3131,3254,3255,1104,1577, # 3936 + 227,1351,1645,2453,2193,1421,2887, 812,2121, 634, 95,2435, 201,2312,4665,1646, # 3952 +1671,2743,1601,2554,2702,2648,2280,1315,1366,2089,3132,1573,3718,3965,1729,1189, # 3968 + 328,2679,1077,1940,1136, 558,1283, 964,1195, 621,2074,1199,1743,3460,3619,1896, # 3984 +1916,1890,3836,2952,1154,2112,1064, 862, 378,3011,2066,2113,2803,1568,2839,6178, # 4000 +3088,2919,1941,1660,2004,1992,2194, 142, 707,1590,1708,1624,1922,1023,1836,1233, # 4016 +1004,2313, 789, 741,3620,6179,1609,2411,1200,4127,3719,3720,4666,2057,3721, 593, # 4032 +2840, 367,2920,1878,6180,3461,1521, 628,1168, 692,2211,2649, 300, 720,2067,2571, # 4048 +2953,3396, 959,2504,3966,3539,3462,1977, 701,6181, 954,1043, 800, 681, 183,3722, # 4064 +1803,1730,3540,4128,2103, 815,2314, 174, 467, 230,2454,1093,2134, 755,3541,3397, # 4080 +1141,1162,6182,1738,2039, 270,3256,2513,1005,1647,2185,3837, 858,1679,1897,1719, # 4096 +2954,2324,1806, 402, 670, 167,4129,1498,2158,2104, 750,6183, 915, 189,1680,1551, # 4112 + 455,4356,1501,2455, 405,1095,2955, 338,1586,1266,1819, 570, 641,1324, 237,1556, # 4128 +2650,1388,3723,6184,1368,2384,1343,1978,3089,2436, 879,3724, 792,1191, 758,3012, # 4144 +1411,2135,1322,4357, 240,4667,1848,3725,1574,6185, 420,3045,1546,1391, 714,4358, # 4160 +1967, 941,1864, 863, 664, 426, 560,1731,2680,1785,2864,1949,2363, 403,3330,1415, # 4176 +1279,2136,1697,2335, 204, 721,2097,3838, 90,6186,2085,2505, 191,3967, 124,2148, # 4192 +1376,1798,1178,1107,1898,1405, 860,4359,1243,1272,2375,2983,1558,2456,1638, 113, # 4208 +3621, 578,1923,2609, 880, 386,4130, 784,2186,2266,1422,2956,2172,1722, 497, 263, # 4224 +2514,1267,2412,2610, 177,2703,3542, 774,1927,1344, 616,1432,1595,1018, 172,4360, # 4240 +2325, 911,4361, 438,1468,3622, 794,3968,2024,2173,1681,1829,2957, 945, 895,3090, # 4256 + 575,2212,2476, 475,2401,2681, 785,2744,1745,2293,2555,1975,3133,2865, 394,4668, # 4272 +3839, 635,4131, 639, 202,1507,2195,2766,1345,1435,2572,3726,1908,1184,1181,2457, # 4288 +3727,3134,4362, 843,2611, 437, 916,4669, 234, 769,1884,3046,3047,3623, 833,6187, # 4304 +1639,2250,2402,1355,1185,2010,2047, 999, 525,1732,1290,1488,2612, 948,1578,3728, # 4320 +2413,2477,1216,2725,2159, 334,3840,1328,3624,2921,1525,4132, 564,1056, 891,4363, # 4336 +1444,1698,2385,2251,3729,1365,2281,2235,1717,6188, 864,3841,2515, 444, 527,2767, # 4352 +2922,3625, 544, 461,6189, 566, 209,2437,3398,2098,1065,2068,3331,3626,3257,2137, # 4368 #last 512 +) + + diff --git a/robot/lib/python3.8/site-packages/chardet/jpcntx.py b/robot/lib/python3.8/site-packages/chardet/jpcntx.py new file mode 100644 index 0000000000000000000000000000000000000000..20044e4bc8f5a9775d82be0ecf387ce752732507 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/jpcntx.py @@ -0,0 +1,233 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + + +# This is hiragana 2-char sequence table, the number in each cell represents its frequency category +jp2CharContext = ( +(0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1), +(2,4,0,4,0,3,0,4,0,3,4,4,4,2,4,3,3,4,3,2,3,3,4,2,3,3,3,2,4,1,4,3,3,1,5,4,3,4,3,4,3,5,3,0,3,5,4,2,0,3,1,0,3,3,0,3,3,0,1,1,0,4,3,0,3,3,0,4,0,2,0,3,5,5,5,5,4,0,4,1,0,3,4), +(0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2), +(0,4,0,5,0,5,0,4,0,4,5,4,4,3,5,3,5,1,5,3,4,3,4,4,3,4,3,3,4,3,5,4,4,3,5,5,3,5,5,5,3,5,5,3,4,5,5,3,1,3,2,0,3,4,0,4,2,0,4,2,1,5,3,2,3,5,0,4,0,2,0,5,4,4,5,4,5,0,4,0,0,4,4), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), +(0,3,0,4,0,3,0,3,0,4,5,4,3,3,3,3,4,3,5,4,4,3,5,4,4,3,4,3,4,4,4,4,5,3,4,4,3,4,5,5,4,5,5,1,4,5,4,3,0,3,3,1,3,3,0,4,4,0,3,3,1,5,3,3,3,5,0,4,0,3,0,4,4,3,4,3,3,0,4,1,1,3,4), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), +(0,4,0,3,0,3,0,4,0,3,4,4,3,2,2,1,2,1,3,1,3,3,3,3,3,4,3,1,3,3,5,3,3,0,4,3,0,5,4,3,3,5,4,4,3,4,4,5,0,1,2,0,1,2,0,2,2,0,1,0,0,5,2,2,1,4,0,3,0,1,0,4,4,3,5,4,3,0,2,1,0,4,3), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), +(0,3,0,5,0,4,0,2,1,4,4,2,4,1,4,2,4,2,4,3,3,3,4,3,3,3,3,1,4,2,3,3,3,1,4,4,1,1,1,4,3,3,2,0,2,4,3,2,0,3,3,0,3,1,1,0,0,0,3,3,0,4,2,2,3,4,0,4,0,3,0,4,4,5,3,4,4,0,3,0,0,1,4), +(1,4,0,4,0,4,0,4,0,3,5,4,4,3,4,3,5,4,3,3,4,3,5,4,4,4,4,3,4,2,4,3,3,1,5,4,3,2,4,5,4,5,5,4,4,5,4,4,0,3,2,2,3,3,0,4,3,1,3,2,1,4,3,3,4,5,0,3,0,2,0,4,5,5,4,5,4,0,4,0,0,5,4), +(0,5,0,5,0,4,0,3,0,4,4,3,4,3,3,3,4,0,4,4,4,3,4,3,4,3,3,1,4,2,4,3,4,0,5,4,1,4,5,4,4,5,3,2,4,3,4,3,2,4,1,3,3,3,2,3,2,0,4,3,3,4,3,3,3,4,0,4,0,3,0,4,5,4,4,4,3,0,4,1,0,1,3), +(0,3,1,4,0,3,0,2,0,3,4,4,3,1,4,2,3,3,4,3,4,3,4,3,4,4,3,2,3,1,5,4,4,1,4,4,3,5,4,4,3,5,5,4,3,4,4,3,1,2,3,1,2,2,0,3,2,0,3,1,0,5,3,3,3,4,3,3,3,3,4,4,4,4,5,4,2,0,3,3,2,4,3), +(0,2,0,3,0,1,0,1,0,0,3,2,0,0,2,0,1,0,2,1,3,3,3,1,2,3,1,0,1,0,4,2,1,1,3,3,0,4,3,3,1,4,3,3,0,3,3,2,0,0,0,0,1,0,0,2,0,0,0,0,0,4,1,0,2,3,2,2,2,1,3,3,3,4,4,3,2,0,3,1,0,3,3), +(0,4,0,4,0,3,0,3,0,4,4,4,3,3,3,3,3,3,4,3,4,2,4,3,4,3,3,2,4,3,4,5,4,1,4,5,3,5,4,5,3,5,4,0,3,5,5,3,1,3,3,2,2,3,0,3,4,1,3,3,2,4,3,3,3,4,0,4,0,3,0,4,5,4,4,5,3,0,4,1,0,3,4), +(0,2,0,3,0,3,0,0,0,2,2,2,1,0,1,0,0,0,3,0,3,0,3,0,1,3,1,0,3,1,3,3,3,1,3,3,3,0,1,3,1,3,4,0,0,3,1,1,0,3,2,0,0,0,0,1,3,0,1,0,0,3,3,2,0,3,0,0,0,0,0,3,4,3,4,3,3,0,3,0,0,2,3), +(2,3,0,3,0,2,0,1,0,3,3,4,3,1,3,1,1,1,3,1,4,3,4,3,3,3,0,0,3,1,5,4,3,1,4,3,2,5,5,4,4,4,4,3,3,4,4,4,0,2,1,1,3,2,0,1,2,0,0,1,0,4,1,3,3,3,0,3,0,1,0,4,4,4,5,5,3,0,2,0,0,4,4), +(0,2,0,1,0,3,1,3,0,2,3,3,3,0,3,1,0,0,3,0,3,2,3,1,3,2,1,1,0,0,4,2,1,0,2,3,1,4,3,2,0,4,4,3,1,3,1,3,0,1,0,0,1,0,0,0,1,0,0,0,0,4,1,1,1,2,0,3,0,0,0,3,4,2,4,3,2,0,1,0,0,3,3), +(0,1,0,4,0,5,0,4,0,2,4,4,2,3,3,2,3,3,5,3,3,3,4,3,4,2,3,0,4,3,3,3,4,1,4,3,2,1,5,5,3,4,5,1,3,5,4,2,0,3,3,0,1,3,0,4,2,0,1,3,1,4,3,3,3,3,0,3,0,1,0,3,4,4,4,5,5,0,3,0,1,4,5), +(0,2,0,3,0,3,0,0,0,2,3,1,3,0,4,0,1,1,3,0,3,4,3,2,3,1,0,3,3,2,3,1,3,0,2,3,0,2,1,4,1,2,2,0,0,3,3,0,0,2,0,0,0,1,0,0,0,0,2,2,0,3,2,1,3,3,0,2,0,2,0,0,3,3,1,2,4,0,3,0,2,2,3), +(2,4,0,5,0,4,0,4,0,2,4,4,4,3,4,3,3,3,1,2,4,3,4,3,4,4,5,0,3,3,3,3,2,0,4,3,1,4,3,4,1,4,4,3,3,4,4,3,1,2,3,0,4,2,0,4,1,0,3,3,0,4,3,3,3,4,0,4,0,2,0,3,5,3,4,5,2,0,3,0,0,4,5), +(0,3,0,4,0,1,0,1,0,1,3,2,2,1,3,0,3,0,2,0,2,0,3,0,2,0,0,0,1,0,1,1,0,0,3,1,0,0,0,4,0,3,1,0,2,1,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,4,2,2,3,1,0,3,0,0,0,1,4,4,4,3,0,0,4,0,0,1,4), +(1,4,1,5,0,3,0,3,0,4,5,4,4,3,5,3,3,4,4,3,4,1,3,3,3,3,2,1,4,1,5,4,3,1,4,4,3,5,4,4,3,5,4,3,3,4,4,4,0,3,3,1,2,3,0,3,1,0,3,3,0,5,4,4,4,4,4,4,3,3,5,4,4,3,3,5,4,0,3,2,0,4,4), +(0,2,0,3,0,1,0,0,0,1,3,3,3,2,4,1,3,0,3,1,3,0,2,2,1,1,0,0,2,0,4,3,1,0,4,3,0,4,4,4,1,4,3,1,1,3,3,1,0,2,0,0,1,3,0,0,0,0,2,0,0,4,3,2,4,3,5,4,3,3,3,4,3,3,4,3,3,0,2,1,0,3,3), +(0,2,0,4,0,3,0,2,0,2,5,5,3,4,4,4,4,1,4,3,3,0,4,3,4,3,1,3,3,2,4,3,0,3,4,3,0,3,4,4,2,4,4,0,4,5,3,3,2,2,1,1,1,2,0,1,5,0,3,3,2,4,3,3,3,4,0,3,0,2,0,4,4,3,5,5,0,0,3,0,2,3,3), +(0,3,0,4,0,3,0,1,0,3,4,3,3,1,3,3,3,0,3,1,3,0,4,3,3,1,1,0,3,0,3,3,0,0,4,4,0,1,5,4,3,3,5,0,3,3,4,3,0,2,0,1,1,1,0,1,3,0,1,2,1,3,3,2,3,3,0,3,0,1,0,1,3,3,4,4,1,0,1,2,2,1,3), +(0,1,0,4,0,4,0,3,0,1,3,3,3,2,3,1,1,0,3,0,3,3,4,3,2,4,2,0,1,0,4,3,2,0,4,3,0,5,3,3,2,4,4,4,3,3,3,4,0,1,3,0,0,1,0,0,1,0,0,0,0,4,2,3,3,3,0,3,0,0,0,4,4,4,5,3,2,0,3,3,0,3,5), +(0,2,0,3,0,0,0,3,0,1,3,0,2,0,0,0,1,0,3,1,1,3,3,0,0,3,0,0,3,0,2,3,1,0,3,1,0,3,3,2,0,4,2,2,0,2,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,2,1,2,0,1,0,1,0,0,0,1,3,1,2,0,0,0,1,0,0,1,4), +(0,3,0,3,0,5,0,1,0,2,4,3,1,3,3,2,1,1,5,2,1,0,5,1,2,0,0,0,3,3,2,2,3,2,4,3,0,0,3,3,1,3,3,0,2,5,3,4,0,3,3,0,1,2,0,2,2,0,3,2,0,2,2,3,3,3,0,2,0,1,0,3,4,4,2,5,4,0,3,0,0,3,5), +(0,3,0,3,0,3,0,1,0,3,3,3,3,0,3,0,2,0,2,1,1,0,2,0,1,0,0,0,2,1,0,0,1,0,3,2,0,0,3,3,1,2,3,1,0,3,3,0,0,1,0,0,0,0,0,2,0,0,0,0,0,2,3,1,2,3,0,3,0,1,0,3,2,1,0,4,3,0,1,1,0,3,3), +(0,4,0,5,0,3,0,3,0,4,5,5,4,3,5,3,4,3,5,3,3,2,5,3,4,4,4,3,4,3,4,5,5,3,4,4,3,4,4,5,4,4,4,3,4,5,5,4,2,3,4,2,3,4,0,3,3,1,4,3,2,4,3,3,5,5,0,3,0,3,0,5,5,5,5,4,4,0,4,0,1,4,4), +(0,4,0,4,0,3,0,3,0,3,5,4,4,2,3,2,5,1,3,2,5,1,4,2,3,2,3,3,4,3,3,3,3,2,5,4,1,3,3,5,3,4,4,0,4,4,3,1,1,3,1,0,2,3,0,2,3,0,3,0,0,4,3,1,3,4,0,3,0,2,0,4,4,4,3,4,5,0,4,0,0,3,4), +(0,3,0,3,0,3,1,2,0,3,4,4,3,3,3,0,2,2,4,3,3,1,3,3,3,1,1,0,3,1,4,3,2,3,4,4,2,4,4,4,3,4,4,3,2,4,4,3,1,3,3,1,3,3,0,4,1,0,2,2,1,4,3,2,3,3,5,4,3,3,5,4,4,3,3,0,4,0,3,2,2,4,4), +(0,2,0,1,0,0,0,0,0,1,2,1,3,0,0,0,0,0,2,0,1,2,1,0,0,1,0,0,0,0,3,0,0,1,0,1,1,3,1,0,0,0,1,1,0,1,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,1,2,2,0,3,4,0,0,0,1,1,0,0,1,0,0,0,0,0,1,1), +(0,1,0,0,0,1,0,0,0,0,4,0,4,1,4,0,3,0,4,0,3,0,4,0,3,0,3,0,4,1,5,1,4,0,0,3,0,5,0,5,2,0,1,0,0,0,2,1,4,0,1,3,0,0,3,0,0,3,1,1,4,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0), +(1,4,0,5,0,3,0,2,0,3,5,4,4,3,4,3,5,3,4,3,3,0,4,3,3,3,3,3,3,2,4,4,3,1,3,4,4,5,4,4,3,4,4,1,3,5,4,3,3,3,1,2,2,3,3,1,3,1,3,3,3,5,3,3,4,5,0,3,0,3,0,3,4,3,4,4,3,0,3,0,2,4,3), +(0,1,0,4,0,0,0,0,0,1,4,0,4,1,4,2,4,0,3,0,1,0,1,0,0,0,0,0,2,0,3,1,1,1,0,3,0,0,0,1,2,1,0,0,1,1,1,1,0,1,0,0,0,1,0,0,3,0,0,0,0,3,2,0,2,2,0,1,0,0,0,2,3,2,3,3,0,0,0,0,2,1,0), +(0,5,1,5,0,3,0,3,0,5,4,4,5,1,5,3,3,0,4,3,4,3,5,3,4,3,3,2,4,3,4,3,3,0,3,3,1,4,4,3,4,4,4,3,4,5,5,3,2,3,1,1,3,3,1,3,1,1,3,3,2,4,5,3,3,5,0,4,0,3,0,4,4,3,5,3,3,0,3,4,0,4,3), +(0,5,0,5,0,3,0,2,0,4,4,3,5,2,4,3,3,3,4,4,4,3,5,3,5,3,3,1,4,0,4,3,3,0,3,3,0,4,4,4,4,5,4,3,3,5,5,3,2,3,1,2,3,2,0,1,0,0,3,2,2,4,4,3,1,5,0,4,0,3,0,4,3,1,3,2,1,0,3,3,0,3,3), +(0,4,0,5,0,5,0,4,0,4,5,5,5,3,4,3,3,2,5,4,4,3,5,3,5,3,4,0,4,3,4,4,3,2,4,4,3,4,5,4,4,5,5,0,3,5,5,4,1,3,3,2,3,3,1,3,1,0,4,3,1,4,4,3,4,5,0,4,0,2,0,4,3,4,4,3,3,0,4,0,0,5,5), +(0,4,0,4,0,5,0,1,1,3,3,4,4,3,4,1,3,0,5,1,3,0,3,1,3,1,1,0,3,0,3,3,4,0,4,3,0,4,4,4,3,4,4,0,3,5,4,1,0,3,0,0,2,3,0,3,1,0,3,1,0,3,2,1,3,5,0,3,0,1,0,3,2,3,3,4,4,0,2,2,0,4,4), +(2,4,0,5,0,4,0,3,0,4,5,5,4,3,5,3,5,3,5,3,5,2,5,3,4,3,3,4,3,4,5,3,2,1,5,4,3,2,3,4,5,3,4,1,2,5,4,3,0,3,3,0,3,2,0,2,3,0,4,1,0,3,4,3,3,5,0,3,0,1,0,4,5,5,5,4,3,0,4,2,0,3,5), +(0,5,0,4,0,4,0,2,0,5,4,3,4,3,4,3,3,3,4,3,4,2,5,3,5,3,4,1,4,3,4,4,4,0,3,5,0,4,4,4,4,5,3,1,3,4,5,3,3,3,3,3,3,3,0,2,2,0,3,3,2,4,3,3,3,5,3,4,1,3,3,5,3,2,0,0,0,0,4,3,1,3,3), +(0,1,0,3,0,3,0,1,0,1,3,3,3,2,3,3,3,0,3,0,0,0,3,1,3,0,0,0,2,2,2,3,0,0,3,2,0,1,2,4,1,3,3,0,0,3,3,3,0,1,0,0,2,1,0,0,3,0,3,1,0,3,0,0,1,3,0,2,0,1,0,3,3,1,3,3,0,0,1,1,0,3,3), +(0,2,0,3,0,2,1,4,0,2,2,3,1,1,3,1,1,0,2,0,3,1,2,3,1,3,0,0,1,0,4,3,2,3,3,3,1,4,2,3,3,3,3,1,0,3,1,4,0,1,1,0,1,2,0,1,1,0,1,1,0,3,1,3,2,2,0,1,0,0,0,2,3,3,3,1,0,0,0,0,0,2,3), +(0,5,0,4,0,5,0,2,0,4,5,5,3,3,4,3,3,1,5,4,4,2,4,4,4,3,4,2,4,3,5,5,4,3,3,4,3,3,5,5,4,5,5,1,3,4,5,3,1,4,3,1,3,3,0,3,3,1,4,3,1,4,5,3,3,5,0,4,0,3,0,5,3,3,1,4,3,0,4,0,1,5,3), +(0,5,0,5,0,4,0,2,0,4,4,3,4,3,3,3,3,3,5,4,4,4,4,4,4,5,3,3,5,2,4,4,4,3,4,4,3,3,4,4,5,5,3,3,4,3,4,3,3,4,3,3,3,3,1,2,2,1,4,3,3,5,4,4,3,4,0,4,0,3,0,4,4,4,4,4,1,0,4,2,0,2,4), +(0,4,0,4,0,3,0,1,0,3,5,2,3,0,3,0,2,1,4,2,3,3,4,1,4,3,3,2,4,1,3,3,3,0,3,3,0,0,3,3,3,5,3,3,3,3,3,2,0,2,0,0,2,0,0,2,0,0,1,0,0,3,1,2,2,3,0,3,0,2,0,4,4,3,3,4,1,0,3,0,0,2,4), +(0,0,0,4,0,0,0,0,0,0,1,0,1,0,2,0,0,0,0,0,1,0,2,0,1,0,0,0,0,0,3,1,3,0,3,2,0,0,0,1,0,3,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,0,2,0,0,0,0,0,0,2), +(0,2,1,3,0,2,0,2,0,3,3,3,3,1,3,1,3,3,3,3,3,3,4,2,2,1,2,1,4,0,4,3,1,3,3,3,2,4,3,5,4,3,3,3,3,3,3,3,0,1,3,0,2,0,0,1,0,0,1,0,0,4,2,0,2,3,0,3,3,0,3,3,4,2,3,1,4,0,1,2,0,2,3), +(0,3,0,3,0,1,0,3,0,2,3,3,3,0,3,1,2,0,3,3,2,3,3,2,3,2,3,1,3,0,4,3,2,0,3,3,1,4,3,3,2,3,4,3,1,3,3,1,1,0,1,1,0,1,0,1,0,1,0,0,0,4,1,1,0,3,0,3,1,0,2,3,3,3,3,3,1,0,0,2,0,3,3), +(0,0,0,0,0,0,0,0,0,0,3,0,2,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,3,0,3,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,2,0,2,3,0,0,0,0,0,0,0,0,3), +(0,2,0,3,1,3,0,3,0,2,3,3,3,1,3,1,3,1,3,1,3,3,3,1,3,0,2,3,1,1,4,3,3,2,3,3,1,2,2,4,1,3,3,0,1,4,2,3,0,1,3,0,3,0,0,1,3,0,2,0,0,3,3,2,1,3,0,3,0,2,0,3,4,4,4,3,1,0,3,0,0,3,3), +(0,2,0,1,0,2,0,0,0,1,3,2,2,1,3,0,1,1,3,0,3,2,3,1,2,0,2,0,1,1,3,3,3,0,3,3,1,1,2,3,2,3,3,1,2,3,2,0,0,1,0,0,0,0,0,0,3,0,1,0,0,2,1,2,1,3,0,3,0,0,0,3,4,4,4,3,2,0,2,0,0,2,4), +(0,0,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,2,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,3,1,0,0,0,0,0,0,0,3), +(0,3,0,3,0,2,0,3,0,3,3,3,2,3,2,2,2,0,3,1,3,3,3,2,3,3,0,0,3,0,3,2,2,0,2,3,1,4,3,4,3,3,2,3,1,5,4,4,0,3,1,2,1,3,0,3,1,1,2,0,2,3,1,3,1,3,0,3,0,1,0,3,3,4,4,2,1,0,2,1,0,2,4), +(0,1,0,3,0,1,0,2,0,1,4,2,5,1,4,0,2,0,2,1,3,1,4,0,2,1,0,0,2,1,4,1,1,0,3,3,0,5,1,3,2,3,3,1,0,3,2,3,0,1,0,0,0,0,0,0,1,0,0,0,0,4,0,1,0,3,0,2,0,1,0,3,3,3,4,3,3,0,0,0,0,2,3), +(0,0,0,1,0,0,0,0,0,0,2,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,1,0,0,0,0,0,3), +(0,1,0,3,0,4,0,3,0,2,4,3,1,0,3,2,2,1,3,1,2,2,3,1,1,1,2,1,3,0,1,2,0,1,3,2,1,3,0,5,5,1,0,0,1,3,2,1,0,3,0,0,1,0,0,0,0,0,3,4,0,1,1,1,3,2,0,2,0,1,0,2,3,3,1,2,3,0,1,0,1,0,4), +(0,0,0,1,0,3,0,3,0,2,2,1,0,0,4,0,3,0,3,1,3,0,3,0,3,0,1,0,3,0,3,1,3,0,3,3,0,0,1,2,1,1,1,0,1,2,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,2,2,1,2,0,0,2,0,0,0,0,2,3,3,3,3,0,0,0,0,1,4), +(0,0,0,3,0,3,0,0,0,0,3,1,1,0,3,0,1,0,2,0,1,0,0,0,0,0,0,0,1,0,3,0,2,0,2,3,0,0,2,2,3,1,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,2,3), +(2,4,0,5,0,5,0,4,0,3,4,3,3,3,4,3,3,3,4,3,4,4,5,4,5,5,5,2,3,0,5,5,4,1,5,4,3,1,5,4,3,4,4,3,3,4,3,3,0,3,2,0,2,3,0,3,0,0,3,3,0,5,3,2,3,3,0,3,0,3,0,3,4,5,4,5,3,0,4,3,0,3,4), +(0,3,0,3,0,3,0,3,0,3,3,4,3,2,3,2,3,0,4,3,3,3,3,3,3,3,3,0,3,2,4,3,3,1,3,4,3,4,4,4,3,4,4,3,2,4,4,1,0,2,0,0,1,1,0,2,0,0,3,1,0,5,3,2,1,3,0,3,0,1,2,4,3,2,4,3,3,0,3,2,0,4,4), +(0,3,0,3,0,1,0,0,0,1,4,3,3,2,3,1,3,1,4,2,3,2,4,2,3,4,3,0,2,2,3,3,3,0,3,3,3,0,3,4,1,3,3,0,3,4,3,3,0,1,1,0,1,0,0,0,4,0,3,0,0,3,1,2,1,3,0,4,0,1,0,4,3,3,4,3,3,0,2,0,0,3,3), +(0,3,0,4,0,1,0,3,0,3,4,3,3,0,3,3,3,1,3,1,3,3,4,3,3,3,0,0,3,1,5,3,3,1,3,3,2,5,4,3,3,4,5,3,2,5,3,4,0,1,0,0,0,0,0,2,0,0,1,1,0,4,2,2,1,3,0,3,0,2,0,4,4,3,5,3,2,0,1,1,0,3,4), +(0,5,0,4,0,5,0,2,0,4,4,3,3,2,3,3,3,1,4,3,4,1,5,3,4,3,4,0,4,2,4,3,4,1,5,4,0,4,4,4,4,5,4,1,3,5,4,2,1,4,1,1,3,2,0,3,1,0,3,2,1,4,3,3,3,4,0,4,0,3,0,4,4,4,3,3,3,0,4,2,0,3,4), +(1,4,0,4,0,3,0,1,0,3,3,3,1,1,3,3,2,2,3,3,1,0,3,2,2,1,2,0,3,1,2,1,2,0,3,2,0,2,2,3,3,4,3,0,3,3,1,2,0,1,1,3,1,2,0,0,3,0,1,1,0,3,2,2,3,3,0,3,0,0,0,2,3,3,4,3,3,0,1,0,0,1,4), +(0,4,0,4,0,4,0,0,0,3,4,4,3,1,4,2,3,2,3,3,3,1,4,3,4,0,3,0,4,2,3,3,2,2,5,4,2,1,3,4,3,4,3,1,3,3,4,2,0,2,1,0,3,3,0,0,2,0,3,1,0,4,4,3,4,3,0,4,0,1,0,2,4,4,4,4,4,0,3,2,0,3,3), +(0,0,0,1,0,4,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,3,2,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,2), +(0,2,0,3,0,4,0,4,0,1,3,3,3,0,4,0,2,1,2,1,1,1,2,0,3,1,1,0,1,0,3,1,0,0,3,3,2,0,1,1,0,0,0,0,0,1,0,2,0,2,2,0,3,1,0,0,1,0,1,1,0,1,2,0,3,0,0,0,0,1,0,0,3,3,4,3,1,0,1,0,3,0,2), +(0,0,0,3,0,5,0,0,0,0,1,0,2,0,3,1,0,1,3,0,0,0,2,0,0,0,1,0,0,0,1,1,0,0,4,0,0,0,2,3,0,1,4,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,1,0,0,0,0,0,0,0,2,0,0,3,0,0,0,0,0,3), +(0,2,0,5,0,5,0,1,0,2,4,3,3,2,5,1,3,2,3,3,3,0,4,1,2,0,3,0,4,0,2,2,1,1,5,3,0,0,1,4,2,3,2,0,3,3,3,2,0,2,4,1,1,2,0,1,1,0,3,1,0,1,3,1,2,3,0,2,0,0,0,1,3,5,4,4,4,0,3,0,0,1,3), +(0,4,0,5,0,4,0,4,0,4,5,4,3,3,4,3,3,3,4,3,4,4,5,3,4,5,4,2,4,2,3,4,3,1,4,4,1,3,5,4,4,5,5,4,4,5,5,5,2,3,3,1,4,3,1,3,3,0,3,3,1,4,3,4,4,4,0,3,0,4,0,3,3,4,4,5,0,0,4,3,0,4,5), +(0,4,0,4,0,3,0,3,0,3,4,4,4,3,3,2,4,3,4,3,4,3,5,3,4,3,2,1,4,2,4,4,3,1,3,4,2,4,5,5,3,4,5,4,1,5,4,3,0,3,2,2,3,2,1,3,1,0,3,3,3,5,3,3,3,5,4,4,2,3,3,4,3,3,3,2,1,0,3,2,1,4,3), +(0,4,0,5,0,4,0,3,0,3,5,5,3,2,4,3,4,0,5,4,4,1,4,4,4,3,3,3,4,3,5,5,2,3,3,4,1,2,5,5,3,5,5,2,3,5,5,4,0,3,2,0,3,3,1,1,5,1,4,1,0,4,3,2,3,5,0,4,0,3,0,5,4,3,4,3,0,0,4,1,0,4,4), +(1,3,0,4,0,2,0,2,0,2,5,5,3,3,3,3,3,0,4,2,3,4,4,4,3,4,0,0,3,4,5,4,3,3,3,3,2,5,5,4,5,5,5,4,3,5,5,5,1,3,1,0,1,0,0,3,2,0,4,2,0,5,2,3,2,4,1,3,0,3,0,4,5,4,5,4,3,0,4,2,0,5,4), +(0,3,0,4,0,5,0,3,0,3,4,4,3,2,3,2,3,3,3,3,3,2,4,3,3,2,2,0,3,3,3,3,3,1,3,3,3,0,4,4,3,4,4,1,1,4,4,2,0,3,1,0,1,1,0,4,1,0,2,3,1,3,3,1,3,4,0,3,0,1,0,3,1,3,0,0,1,0,2,0,0,4,4), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), +(0,3,0,3,0,2,0,3,0,1,5,4,3,3,3,1,4,2,1,2,3,4,4,2,4,4,5,0,3,1,4,3,4,0,4,3,3,3,2,3,2,5,3,4,3,2,2,3,0,0,3,0,2,1,0,1,2,0,0,0,0,2,1,1,3,1,0,2,0,4,0,3,4,4,4,5,2,0,2,0,0,1,3), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,1,0,0,1,1,0,0,0,4,2,1,1,0,1,0,3,2,0,0,3,1,1,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,1,0,0,0,2,0,0,0,1,4,0,4,2,1,0,0,0,0,0,1), +(0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,1,0,1,0,0,0,0,3,1,0,0,0,2,0,2,1,0,0,1,2,1,0,1,1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,1,3,1,0,0,0,0,0,1,0,0,2,1,0,0,0,0,0,0,0,0,2), +(0,4,0,4,0,4,0,3,0,4,4,3,4,2,4,3,2,0,4,4,4,3,5,3,5,3,3,2,4,2,4,3,4,3,1,4,0,2,3,4,4,4,3,3,3,4,4,4,3,4,1,3,4,3,2,1,2,1,3,3,3,4,4,3,3,5,0,4,0,3,0,4,3,3,3,2,1,0,3,0,0,3,3), +(0,4,0,3,0,3,0,3,0,3,5,5,3,3,3,3,4,3,4,3,3,3,4,4,4,3,3,3,3,4,3,5,3,3,1,3,2,4,5,5,5,5,4,3,4,5,5,3,2,2,3,3,3,3,2,3,3,1,2,3,2,4,3,3,3,4,0,4,0,2,0,4,3,2,2,1,2,0,3,0,0,4,1), +) + +class JapaneseContextAnalysis(object): + NUM_OF_CATEGORY = 6 + DONT_KNOW = -1 + ENOUGH_REL_THRESHOLD = 100 + MAX_REL_THRESHOLD = 1000 + MINIMUM_DATA_THRESHOLD = 4 + + def __init__(self): + self._total_rel = None + self._rel_sample = None + self._need_to_skip_char_num = None + self._last_char_order = None + self._done = None + self.reset() + + def reset(self): + self._total_rel = 0 # total sequence received + # category counters, each integer counts sequence in its category + self._rel_sample = [0] * self.NUM_OF_CATEGORY + # if last byte in current buffer is not the last byte of a character, + # we need to know how many bytes to skip in next buffer + self._need_to_skip_char_num = 0 + self._last_char_order = -1 # The order of previous char + # If this flag is set to True, detection is done and conclusion has + # been made + self._done = False + + def feed(self, byte_str, num_bytes): + if self._done: + return + + # The buffer we got is byte oriented, and a character may span in more than one + # buffers. In case the last one or two byte in last buffer is not + # complete, we record how many byte needed to complete that character + # and skip these bytes here. We can choose to record those bytes as + # well and analyse the character once it is complete, but since a + # character will not make much difference, by simply skipping + # this character will simply our logic and improve performance. + i = self._need_to_skip_char_num + while i < num_bytes: + order, char_len = self.get_order(byte_str[i:i + 2]) + i += char_len + if i > num_bytes: + self._need_to_skip_char_num = i - num_bytes + self._last_char_order = -1 + else: + if (order != -1) and (self._last_char_order != -1): + self._total_rel += 1 + if self._total_rel > self.MAX_REL_THRESHOLD: + self._done = True + break + self._rel_sample[jp2CharContext[self._last_char_order][order]] += 1 + self._last_char_order = order + + def got_enough_data(self): + return self._total_rel > self.ENOUGH_REL_THRESHOLD + + def get_confidence(self): + # This is just one way to calculate confidence. It works well for me. + if self._total_rel > self.MINIMUM_DATA_THRESHOLD: + return (self._total_rel - self._rel_sample[0]) / self._total_rel + else: + return self.DONT_KNOW + + def get_order(self, byte_str): + return -1, 1 + +class SJISContextAnalysis(JapaneseContextAnalysis): + def __init__(self): + super(SJISContextAnalysis, self).__init__() + self._charset_name = "SHIFT_JIS" + + @property + def charset_name(self): + return self._charset_name + + def get_order(self, byte_str): + if not byte_str: + return -1, 1 + # find out current char's byte length + first_char = byte_str[0] + if (0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC): + char_len = 2 + if (first_char == 0x87) or (0xFA <= first_char <= 0xFC): + self._charset_name = "CP932" + else: + char_len = 1 + + # return its order if it is hiragana + if len(byte_str) > 1: + second_char = byte_str[1] + if (first_char == 202) and (0x9F <= second_char <= 0xF1): + return second_char - 0x9F, char_len + + return -1, char_len + +class EUCJPContextAnalysis(JapaneseContextAnalysis): + def get_order(self, byte_str): + if not byte_str: + return -1, 1 + # find out current char's byte length + first_char = byte_str[0] + if (first_char == 0x8E) or (0xA1 <= first_char <= 0xFE): + char_len = 2 + elif first_char == 0x8F: + char_len = 3 + else: + char_len = 1 + + # return its order if it is hiragana + if len(byte_str) > 1: + second_char = byte_str[1] + if (first_char == 0xA4) and (0xA1 <= second_char <= 0xF3): + return second_char - 0xA1, char_len + + return -1, char_len + + diff --git a/robot/lib/python3.8/site-packages/chardet/langbulgarianmodel.py b/robot/lib/python3.8/site-packages/chardet/langbulgarianmodel.py new file mode 100644 index 0000000000000000000000000000000000000000..2aa4fb2e22fc3bfa26b569273c6cb18c4c415dd9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/langbulgarianmodel.py @@ -0,0 +1,228 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +# this table is modified base on win1251BulgarianCharToOrderMap, so +# only number <64 is sure valid + +Latin5_BulgarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 +110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 +253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 +116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 +194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209, # 80 +210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225, # 90 + 81,226,227,228,229,230,105,231,232,233,234,235,236, 45,237,238, # a0 + 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # b0 + 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,239, 67,240, 60, 56, # c0 + 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # d0 + 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,241, 42, 16, # e0 + 62,242,243,244, 58,245, 98,246,247,248,249,250,251, 91,252,253, # f0 +) + +win1251BulgarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 +110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 +253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 +116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 +206,207,208,209,210,211,212,213,120,214,215,216,217,218,219,220, # 80 +221, 78, 64, 83,121, 98,117,105,222,223,224,225,226,227,228,229, # 90 + 88,230,231,232,233,122, 89,106,234,235,236,237,238, 45,239,240, # a0 + 73, 80,118,114,241,242,243,244,245, 62, 58,246,247,248,249,250, # b0 + 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # c0 + 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,251, 67,252, 60, 56, # d0 + 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # e0 + 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,253, 42, 16, # f0 +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 96.9392% +# first 1024 sequences:3.0618% +# rest sequences: 0.2992% +# negative sequences: 0.0020% +BulgarianLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,3,3,3,3,3, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,2,2,1,2,2, +3,1,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,0,1, +0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,3,3,0,3,1,0, +0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,2,3,2,2,1,3,3,3,3,2,2,2,1,1,2,0,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,2,3,2,2,3,3,1,1,2,3,3,2,3,3,3,3,2,1,2,0,2,0,3,0,0, +0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,1,3,3,3,3,3,2,3,2,3,3,3,3,3,2,3,3,1,3,0,3,0,2,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,1,3,3,2,3,3,3,1,3,3,2,3,2,2,2,0,0,2,0,2,0,2,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,3,3,1,2,2,3,2,1,1,2,0,2,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,2,3,3,1,2,3,2,2,2,3,3,3,3,3,2,2,3,1,2,0,2,1,2,0,0, +0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,1,3,3,3,3,3,2,3,3,3,2,3,3,2,3,2,2,2,3,1,2,0,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,3,3,1,1,1,2,2,1,3,1,3,2,2,3,0,0,1,0,1,0,1,0,0, +0,0,0,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,2,2,3,2,2,3,1,2,1,1,1,2,3,1,3,1,2,2,0,1,1,1,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,1,3,2,2,3,3,1,2,3,1,1,3,3,3,3,1,2,2,1,1,1,0,2,0,2,0,1, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,2,2,3,3,3,2,2,1,1,2,0,2,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,0,1,2,1,3,3,2,3,3,3,3,3,2,3,2,1,0,3,1,2,1,2,1,2,3,2,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,1,1,2,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,1,3,3,2,3,3,2,2,2,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,3,0,3,3,3,3,3,2,1,1,2,1,3,3,0,3,1,1,1,1,3,2,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,1,1,3,1,3,3,2,3,2,2,2,3,0,2,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,2,3,3,2,2,3,2,1,1,1,1,1,3,1,3,1,1,0,0,0,1,0,0,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,2,3,2,0,3,2,0,3,0,2,0,0,2,1,3,1,0,0,1,0,0,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,2,1,1,1,1,2,1,1,2,1,1,1,2,2,1,2,1,1,1,0,1,1,0,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,2,1,3,1,1,2,1,3,2,1,1,0,1,2,3,2,1,1,1,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,3,2,2,1,0,1,0,0,1,0,0,0,2,1,0,3,0,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,2,3,2,3,3,1,3,2,1,1,1,2,1,1,2,1,3,0,1,0,0,0,1,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,1,2,2,3,3,2,3,2,2,2,3,1,2,2,1,1,2,1,1,2,2,0,1,1,0,1,0,2,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,1,3,1,0,2,2,1,3,2,1,0,0,2,0,2,0,1,0,0,0,0,0,0,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,1,2,0,2,3,1,2,3,2,0,1,3,1,2,1,1,1,0,0,1,0,0,2,2,2,3, +2,2,2,2,1,2,1,1,2,2,1,1,2,0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1,1,0,1, +3,3,3,3,3,2,1,2,2,1,2,0,2,0,1,0,1,2,1,2,1,1,0,0,0,1,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,2,3,3,1,1,3,1,0,3,2,1,0,0,0,1,2,0,2,0,1,0,0,0,1,0,1,2,1,2,2, +1,1,1,1,1,1,1,2,2,2,1,1,1,1,1,1,1,0,1,2,1,1,1,0,0,0,0,0,1,1,0,0, +3,1,0,1,0,2,3,2,2,2,3,2,2,2,2,2,1,0,2,1,2,1,1,1,0,1,2,1,2,2,2,1, +1,1,2,2,2,2,1,2,1,1,0,1,2,1,2,2,2,1,1,1,0,1,1,1,1,2,0,1,0,0,0,0, +2,3,2,3,3,0,0,2,1,0,2,1,0,0,0,0,2,3,0,2,0,0,0,0,0,1,0,0,2,0,1,2, +2,1,2,1,2,2,1,1,1,2,1,1,1,0,1,2,2,1,1,1,1,1,0,1,1,1,0,0,1,2,0,0, +3,3,2,2,3,0,2,3,1,1,2,0,0,0,1,0,0,2,0,2,0,0,0,1,0,1,0,1,2,0,2,2, +1,1,1,1,2,1,0,1,2,2,2,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,1,0,0, +2,3,2,3,3,0,0,3,0,1,1,0,1,0,0,0,2,2,1,2,0,0,0,0,0,0,0,0,2,0,1,2, +2,2,1,1,1,1,1,2,2,2,1,0,2,0,1,0,1,0,0,1,0,1,0,0,1,0,0,0,0,1,0,0, +3,3,3,3,2,2,2,2,2,0,2,1,1,1,1,2,1,2,1,1,0,2,0,1,0,1,0,0,2,0,1,2, +1,1,1,1,1,1,1,2,2,1,1,0,2,0,1,0,2,0,0,1,1,1,0,0,2,0,0,0,1,1,0,0, +2,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0,0,0,0,1,2,0,1,2, +2,2,2,1,1,2,1,1,2,2,2,1,2,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,0,0, +2,3,3,3,3,0,2,2,0,2,1,0,0,0,1,1,1,2,0,2,0,0,0,3,0,0,0,0,2,0,2,2, +1,1,1,2,1,2,1,1,2,2,2,1,2,0,1,1,1,0,1,1,1,1,0,2,1,0,0,0,1,1,0,0, +2,3,3,3,3,0,2,1,0,0,2,0,0,0,0,0,1,2,0,2,0,0,0,0,0,0,0,0,2,0,1,2, +1,1,1,2,1,1,1,1,2,2,2,0,1,0,1,1,1,0,0,1,1,1,0,0,1,0,0,0,0,1,0,0, +3,3,2,2,3,0,1,0,1,0,0,0,0,0,0,0,1,1,0,3,0,0,0,0,0,0,0,0,1,0,2,2, +1,1,1,1,1,2,1,1,2,2,1,2,2,1,0,1,1,1,1,1,0,1,0,0,1,0,0,0,1,1,0,0, +3,1,0,1,0,2,2,2,2,3,2,1,1,1,2,3,0,0,1,0,2,1,1,0,1,1,1,1,2,1,1,1, +1,2,2,1,2,1,2,2,1,1,0,1,2,1,2,2,1,1,1,0,0,1,1,1,2,1,0,1,0,0,0,0, +2,1,0,1,0,3,1,2,2,2,2,1,2,2,1,1,1,0,2,1,2,2,1,1,2,1,1,0,2,1,1,1, +1,2,2,2,2,2,2,2,1,2,0,1,1,0,2,1,1,1,1,1,0,0,1,1,1,1,0,1,0,0,0,0, +2,1,1,1,1,2,2,2,2,1,2,2,2,1,2,2,1,1,2,1,2,3,2,2,1,1,1,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,3,2,0,1,2,0,1,2,1,1,0,1,0,1,2,1,2,0,0,0,1,1,0,0,0,1,0,0,2, +1,1,0,0,1,1,0,1,1,1,1,0,2,0,1,1,1,0,0,1,1,0,0,0,0,1,0,0,0,1,0,0, +2,0,0,0,0,1,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,2,1,1,1, +1,2,2,2,2,1,1,2,1,2,1,1,1,0,2,1,2,1,1,1,0,2,1,1,1,1,0,1,0,0,0,0, +3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, +1,1,0,1,0,1,1,1,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,3,2,0,0,0,0,1,0,0,0,0,0,0,1,1,0,2,0,0,0,0,0,0,0,0,1,0,1,2, +1,1,1,1,1,1,0,0,2,2,2,2,2,0,1,1,0,1,1,1,1,1,0,0,1,0,0,0,1,1,0,1, +2,3,1,2,1,0,1,1,0,2,2,2,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,0,1,2, +1,1,1,1,2,1,1,1,1,1,1,1,1,0,1,1,0,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0, +2,2,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,0,2,2, +1,1,1,1,1,0,0,1,2,1,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,2,0,0,2,0,1,1,0,0,0,1,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,1,1, +0,0,0,1,1,1,1,1,1,1,1,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,3,2,0,0,1,0,0,1,0,0,0,0,0,0,1,0,2,0,0,0,1,0,0,0,0,0,0,0,2, +1,1,0,0,1,0,0,0,1,1,0,0,1,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +2,1,2,2,2,1,2,1,2,2,1,1,2,1,1,1,0,1,1,1,1,2,0,1,0,1,1,1,1,0,1,1, +1,1,2,1,1,1,1,1,1,0,0,1,2,1,1,1,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0, +1,0,0,1,3,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,1,0,0,1,0,2,0,0,0,0,0,1,1,1,0,1,0,0,0,0,0,0,0,0,2,0,0,1, +0,2,0,1,0,0,1,1,2,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,2,0,1,1,0,2,1,0,1,1,1,0,0,1,0,2,0,1,0,0,0,0,0,0,0,0,0,1, +0,1,0,0,1,0,0,0,1,1,0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,2,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, +0,1,0,1,1,1,0,0,1,1,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +2,0,1,0,0,1,2,1,1,1,1,1,1,2,2,1,0,0,1,0,1,0,0,0,0,1,1,1,1,0,0,0, +1,1,2,1,1,1,1,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,1,2,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, +0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, +0,1,1,0,1,1,1,0,0,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, +1,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,2,0,0,2,0,1,0,0,1,0,0,1, +1,1,0,0,1,1,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, +1,1,1,1,1,1,1,2,0,0,0,0,0,0,2,1,0,1,1,0,0,1,1,1,0,1,0,0,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,1,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +) + +Latin5BulgarianModel = { + 'char_to_order_map': Latin5_BulgarianCharToOrderMap, + 'precedence_matrix': BulgarianLangModel, + 'typical_positive_ratio': 0.969392, + 'keep_english_letter': False, + 'charset_name': "ISO-8859-5", + 'language': 'Bulgairan', +} + +Win1251BulgarianModel = { + 'char_to_order_map': win1251BulgarianCharToOrderMap, + 'precedence_matrix': BulgarianLangModel, + 'typical_positive_ratio': 0.969392, + 'keep_english_letter': False, + 'charset_name': "windows-1251", + 'language': 'Bulgarian', +} diff --git a/robot/lib/python3.8/site-packages/chardet/langcyrillicmodel.py b/robot/lib/python3.8/site-packages/chardet/langcyrillicmodel.py new file mode 100644 index 0000000000000000000000000000000000000000..e5f9a1fd19cca472d785bf90c6395cd11b524766 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/langcyrillicmodel.py @@ -0,0 +1,333 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# KOI8-R language model +# Character Mapping Table: +KOI8R_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, # 80 +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, # 90 +223,224,225, 68,226,227,228,229,230,231,232,233,234,235,236,237, # a0 +238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253, # b0 + 27, 3, 21, 28, 13, 2, 39, 19, 26, 4, 23, 11, 8, 12, 5, 1, # c0 + 15, 16, 9, 7, 6, 14, 24, 10, 17, 18, 20, 25, 30, 29, 22, 54, # d0 + 59, 37, 44, 58, 41, 48, 53, 46, 55, 42, 60, 36, 49, 38, 31, 34, # e0 + 35, 43, 45, 32, 40, 52, 56, 33, 61, 62, 51, 57, 47, 63, 50, 70, # f0 +) + +win1251_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, +239,240,241,242,243,244,245,246, 68,247,248,249,250,251,252,253, + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, +) + +latin5_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, +239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, +) + +macCyrillic_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, +239,240,241,242,243,244,245,246,247,248,249,250,251,252, 68, 16, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27,255, +) + +IBM855_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194, 68,195,196,197,198,199,200,201,202,203,204,205, +206,207,208,209,210,211,212,213,214,215,216,217, 27, 59, 54, 70, + 3, 37, 21, 44, 28, 58, 13, 41, 2, 48, 39, 53, 19, 46,218,219, +220,221,222,223,224, 26, 55, 4, 42,225,226,227,228, 23, 60,229, +230,231,232,233,234,235, 11, 36,236,237,238,239,240,241,242,243, + 8, 49, 12, 38, 5, 31, 1, 34, 15,244,245,246,247, 35, 16,248, + 43, 9, 45, 7, 32, 6, 40, 14, 52, 24, 56, 10, 33, 17, 61,249, +250, 18, 62, 20, 51, 25, 57, 30, 47, 29, 63, 22, 50,251,252,255, +) + +IBM866_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, +239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 97.6601% +# first 1024 sequences: 2.3389% +# rest sequences: 0.1237% +# negative sequences: 0.0009% +RussianLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,3,3,3,3,1,3,3,3,2,3,2,3,3, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,2,2,2,2,2,0,0,2, +3,3,3,2,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,2,3,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,2,2,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,2,3,3,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, +0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, +0,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,2,2,2,3,1,3,3,1,3,3,3,3,2,2,3,0,2,2,2,3,3,2,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,3,3,3,2,2,3,2,3,3,3,2,1,2,2,0,1,2,2,2,2,2,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,3,0,2,2,3,3,2,1,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,1,2,3,2,2,3,2,3,3,3,3,2,2,3,0,3,2,2,3,1,1,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,3,3,3,3,2,2,2,0,3,3,3,2,2,2,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,2,3,2,2,0,1,3,2,1,2,2,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,2,1,1,3,0,1,1,1,1,2,1,1,0,2,2,2,1,2,0,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,2,2,2,2,1,3,2,3,2,3,2,1,2,2,0,1,1,2,1,2,1,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,2,3,3,3,2,2,2,2,0,2,2,2,2,3,1,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,2,3,2,2,3,3,3,3,3,3,3,3,3,1,3,2,0,0,3,3,3,3,2,3,3,3,3,2,3,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,3,3,2,2,3,3,0,2,1,0,3,2,3,2,3,0,0,1,2,0,0,1,0,1,2,1,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,3,0,2,3,3,3,3,2,3,3,3,3,1,2,2,0,0,2,3,2,2,2,3,2,3,2,2,3,0,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,0,2,3,2,3,0,1,2,3,3,2,0,2,3,0,0,2,3,2,2,0,1,3,1,3,2,2,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,3,0,2,3,3,3,3,3,3,3,3,2,1,3,2,0,0,2,2,3,3,3,2,3,3,0,2,2,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,2,3,3,2,2,2,3,3,0,0,1,1,1,1,1,2,0,0,1,1,1,1,0,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,2,3,3,3,3,3,3,3,0,3,2,3,3,2,3,2,0,2,1,0,1,1,0,1,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,3,2,2,2,2,3,1,3,2,3,1,1,2,1,0,2,2,2,2,1,3,1,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +2,2,3,3,3,3,3,1,2,2,1,3,1,0,3,0,0,3,0,0,0,1,1,0,1,2,1,0,0,0,0,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,2,1,1,3,3,3,2,2,1,2,2,3,1,1,2,0,0,2,2,1,3,0,0,2,1,1,2,1,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,3,3,3,1,2,2,2,1,2,1,3,3,1,1,2,1,2,1,2,2,0,2,0,0,1,1,0,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,3,3,2,1,3,2,2,3,2,0,3,2,0,3,0,1,0,1,1,0,0,1,1,1,1,0,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,2,3,3,3,2,2,2,3,3,1,2,1,2,1,0,1,0,1,1,0,1,0,0,2,1,1,1,0,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +3,1,1,2,1,2,3,3,2,2,1,2,2,3,0,2,1,0,0,2,2,3,2,1,2,2,2,2,2,3,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,1,1,0,1,1,2,2,1,1,3,0,0,1,3,1,1,1,0,0,0,1,0,1,1,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,1,3,3,3,2,0,0,0,2,1,0,1,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,1,0,0,2,3,2,2,2,1,2,2,2,1,2,1,0,0,1,1,1,0,2,0,1,1,1,0,0,1,1, +1,0,0,0,0,0,1,2,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,3,0,0,0,0,1,0,0,0,0,3,0,1,2,1,0,0,0,0,0,0,0,1,1,0,0,1,1, +1,0,1,0,1,2,0,0,1,1,2,1,0,1,1,1,1,0,1,1,1,1,0,1,0,0,1,0,0,1,1,0, +2,2,3,2,2,2,3,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,0,1,0,1,1,1,0,2,1, +1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,0,1,1,0, +3,3,3,2,2,2,2,3,2,2,1,1,2,2,2,2,1,1,3,1,2,1,2,0,0,1,1,0,1,0,2,1, +1,1,1,1,1,2,1,0,1,1,1,1,0,1,0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,1,1,0, +2,0,0,1,0,3,2,2,2,2,1,2,1,2,1,2,0,0,0,2,1,2,2,1,1,2,2,0,1,1,0,2, +1,1,1,1,1,0,1,1,1,2,1,1,1,2,1,0,1,2,1,1,1,1,0,1,1,1,0,0,1,0,0,1, +1,3,2,2,2,1,1,1,2,3,0,0,0,0,2,0,2,2,1,0,0,0,0,0,0,1,0,0,0,0,1,1, +1,0,1,1,0,1,0,1,1,0,1,1,0,2,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, +2,3,2,3,2,1,2,2,2,2,1,0,0,0,2,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,2,1, +1,1,2,1,0,2,0,0,1,0,1,0,0,1,0,0,1,1,0,1,1,0,0,0,0,0,1,0,0,0,0,0, +3,0,0,1,0,2,2,2,3,2,2,2,2,2,2,2,0,0,0,2,1,2,1,1,1,2,2,0,0,0,1,2, +1,1,1,1,1,0,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1, +2,3,2,3,3,2,0,1,1,1,0,0,1,0,2,0,1,1,3,1,0,0,0,0,0,0,0,1,0,0,2,1, +1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,1,0,0,1,1,0,1,0,0,0,0,0,0,1,0, +2,3,3,3,3,1,2,2,2,2,0,1,1,0,2,1,1,1,2,1,0,1,1,0,0,1,0,1,0,0,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,2,0,0,1,1,2,2,1,0,0,2,0,1,1,3,0,0,1,0,0,0,0,0,1,0,1,2,1, +1,1,2,0,1,1,1,0,1,0,1,1,0,1,0,1,1,1,1,0,1,0,0,0,0,0,0,1,0,1,1,0, +1,3,2,3,2,1,0,0,2,2,2,0,1,0,2,0,1,1,1,0,1,0,0,0,3,0,1,1,0,0,2,1, +1,1,1,0,1,1,0,0,0,0,1,1,0,1,0,0,2,1,1,0,1,0,0,0,1,0,1,0,0,1,1,0, +3,1,2,1,1,2,2,2,2,2,2,1,2,2,1,1,0,0,0,2,2,2,0,0,0,1,2,1,0,1,0,1, +2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,2,1,1,1,0,1,0,1,1,0,1,1,1,0,0,1, +3,0,0,0,0,2,0,1,1,1,1,1,1,1,0,1,0,0,0,1,1,1,0,1,0,1,1,0,0,1,0,1, +1,1,0,0,1,0,0,0,1,0,1,1,0,0,1,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,1, +1,3,3,2,2,0,0,0,2,2,0,0,0,1,2,0,1,1,2,0,0,0,0,0,0,0,0,1,0,0,2,1, +0,1,1,0,0,1,1,0,0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0, +2,3,2,3,2,0,0,0,0,1,1,0,0,0,2,0,2,0,2,0,0,0,0,0,1,0,0,1,0,0,1,1, +1,1,2,0,1,2,1,0,1,1,2,1,1,1,1,1,2,1,1,0,1,0,0,1,1,1,1,1,0,1,1,0, +1,3,2,2,2,1,0,0,2,2,1,0,1,2,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,1, +0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,1,0,2,3,1,2,2,2,2,2,2,1,1,0,0,0,1,0,1,0,2,1,1,1,0,0,0,0,1, +1,1,0,1,1,0,1,1,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, +2,0,2,0,0,1,0,3,2,1,2,1,2,2,0,1,0,0,0,2,1,0,0,2,1,1,1,1,0,2,0,2, +2,1,1,1,1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,0,0,0,1,1,1,1,0,1,0,0,1, +1,2,2,2,2,1,0,0,1,0,0,0,0,0,2,0,1,1,1,1,0,0,0,0,1,0,1,2,0,0,2,0, +1,0,1,1,1,2,1,0,1,0,1,1,0,0,1,0,1,1,1,0,1,0,0,0,1,0,0,1,0,1,1,0, +2,1,2,2,2,0,3,0,1,1,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +0,0,0,1,1,1,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0, +1,2,2,3,2,2,0,0,1,1,2,0,1,2,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1, +0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, +2,2,1,1,2,1,2,2,2,2,2,1,2,2,0,1,0,0,0,1,2,2,2,1,2,1,1,1,1,1,2,1, +1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,0,1, +1,2,2,2,2,0,1,0,2,2,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0, +0,0,1,0,0,1,0,0,0,0,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,2,0,0,0,2,2,2,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1, +0,1,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,2,0,0,0,0,1,0,0,1,1,2,0,0,0,0,1,0,1,0,0,1,0,0,2,0,0,0,1, +0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,1,1,2,0,2,1,1,1,1,0,2,2,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1, +0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +1,0,2,1,2,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0, +0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, +1,0,0,0,0,2,0,1,2,1,0,1,1,1,0,1,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,1, +0,0,0,0,0,1,0,0,1,1,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1, +2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,1,0,1,0,1,0,0,1,1,1,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,0,1,1,0,1,0,1,0,0,0,0,1,1,0,1,1,0,0,0,0,0,1,0,1,1,0,1,0,0,0, +0,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, +) + +Koi8rModel = { + 'char_to_order_map': KOI8R_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "KOI8-R", + 'language': 'Russian', +} + +Win1251CyrillicModel = { + 'char_to_order_map': win1251_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "windows-1251", + 'language': 'Russian', +} + +Latin5CyrillicModel = { + 'char_to_order_map': latin5_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "ISO-8859-5", + 'language': 'Russian', +} + +MacCyrillicModel = { + 'char_to_order_map': macCyrillic_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "MacCyrillic", + 'language': 'Russian', +} + +Ibm866Model = { + 'char_to_order_map': IBM866_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "IBM866", + 'language': 'Russian', +} + +Ibm855Model = { + 'char_to_order_map': IBM855_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "IBM855", + 'language': 'Russian', +} diff --git a/robot/lib/python3.8/site-packages/chardet/langgreekmodel.py b/robot/lib/python3.8/site-packages/chardet/langgreekmodel.py new file mode 100644 index 0000000000000000000000000000000000000000..533222166cca9fce442655d9f3098126f50e6140 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/langgreekmodel.py @@ -0,0 +1,225 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +Latin7_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 + 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 +253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 + 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 +253,233, 90,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 +253,253,253,253,247,248, 61, 36, 46, 71, 73,253, 54,253,108,123, # b0 +110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 + 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 +124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 + 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 +) + +win1253_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 + 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 +253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 + 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 +253,233, 61,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 +253,253,253,253,247,253,253, 36, 46, 71, 73,253, 54,253,108,123, # b0 +110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 + 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 +124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 + 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 98.2851% +# first 1024 sequences:1.7001% +# rest sequences: 0.0359% +# negative sequences: 0.0148% +GreekLangModel = ( +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,2,2,3,3,3,3,3,3,3,3,1,3,3,3,0,2,2,3,3,0,3,0,3,2,0,3,3,3,0, +3,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,0,3,3,0,3,2,3,3,0,3,2,3,3,3,0,0,3,0,3,0,3,3,2,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, +0,2,3,2,2,3,3,3,3,3,3,3,3,0,3,3,3,3,0,2,3,3,0,3,3,3,3,2,3,3,3,0, +2,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,2,1,3,3,3,3,2,3,3,2,3,3,2,0, +0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,2,3,3,0, +2,0,1,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,2,3,0,0,0,0,3,3,0,3,1,3,3,3,0,3,3,0,3,3,3,3,0,0,0,0, +2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,0,3,0,3,3,3,3,3,0,3,2,2,2,3,0,2,3,3,3,3,3,2,3,3,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,3,2,2,2,3,3,3,3,0,3,1,3,3,3,3,2,3,3,3,3,3,3,3,2,2,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,2,0,3,0,0,0,3,3,2,3,3,3,3,3,0,0,3,2,3,0,2,3,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,0,3,3,3,3,0,0,3,3,0,2,3,0,3,0,3,3,3,0,0,3,0,3,0,2,2,3,3,0,0, +0,0,1,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,2,0,3,2,3,3,3,3,0,3,3,3,3,3,0,3,3,2,3,2,3,3,2,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,2,3,2,3,3,3,3,3,3,0,2,3,2,3,2,2,2,3,2,3,3,2,3,0,2,2,2,3,0, +2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,0,0,0,3,3,3,2,3,3,0,0,3,0,3,0,0,0,3,2,0,3,0,3,0,0,2,0,2,0, +0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,0,0,0,3,3,0,3,3,3,0,0,1,2,3,0, +3,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,2,0,0,3,2,2,3,3,0,3,3,3,3,3,2,1,3,0,3,2,3,3,2,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,3,0,2,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,3,0,3,2,3,0,0,3,3,3,0, +3,0,0,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,0,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,2,0,3,2,3,0,0,3,2,3,0, +2,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,1,2,2,3,3,3,3,3,3,0,2,3,0,3,0,0,0,3,3,0,3,0,2,0,0,2,3,1,0, +2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,0,3,3,3,3,0,3,0,3,3,2,3,0,3,3,3,3,3,3,0,3,3,3,0,2,3,0,0,3,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,0,3,3,3,0,0,3,0,0,0,3,3,0,3,0,2,3,3,0,0,3,0,3,0,3,3,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,0,0,0,3,3,3,3,3,3,0,0,3,0,2,0,0,0,3,3,0,3,0,3,0,0,2,0,2,0, +0,0,0,0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,3,0,3,0,2,0,3,2,0,3,2,3,2,3,0,0,3,2,3,2,3,3,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,0,0,2,3,3,3,3,3,0,0,0,3,0,2,1,0,0,3,2,2,2,0,3,0,0,2,2,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,0,3,3,3,2,0,3,0,3,0,3,3,0,2,1,2,3,3,0,0,3,0,3,0,3,3,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,3,3,3,0,3,3,3,3,3,3,0,2,3,0,3,0,0,0,2,1,0,2,2,3,0,0,2,2,2,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,0,0,2,3,3,3,2,3,0,0,1,3,0,2,0,0,0,0,3,0,1,0,2,0,0,1,1,1,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,1,0,3,0,0,0,3,2,0,3,2,3,3,3,0,0,3,0,3,2,2,2,1,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,0,3,3,3,0,0,3,0,0,0,0,2,0,2,3,3,2,2,2,2,3,0,2,0,2,2,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,2,0,0,0,0,0,0,2,3,0,2,0,2,3,2,0,0,3,0,3,0,3,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,3,2,3,3,2,2,3,0,2,0,3,0,0,0,2,0,0,0,0,1,2,0,2,0,2,0, +0,2,0,2,0,2,2,0,0,1,0,2,2,2,0,2,2,2,0,2,2,2,0,0,2,0,0,1,0,0,0,0, +0,2,0,3,3,2,0,0,0,0,0,0,1,3,0,2,0,2,2,2,0,0,2,0,3,0,0,2,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,0,2,3,2,0,2,2,0,2,0,2,2,0,2,0,2,2,2,0,0,0,0,0,0,2,3,0,0,0,2, +0,1,2,0,0,0,0,2,2,0,0,0,2,1,0,2,2,0,0,0,0,0,0,1,0,2,0,0,0,0,0,0, +0,0,2,1,0,2,3,2,2,3,2,3,2,0,0,3,3,3,0,0,3,2,0,0,0,1,1,0,2,0,2,2, +0,2,0,2,0,2,2,0,0,2,0,2,2,2,0,2,2,2,2,0,0,2,0,0,0,2,0,1,0,0,0,0, +0,3,0,3,3,2,2,0,3,0,0,0,2,2,0,2,2,2,1,2,0,0,1,2,2,0,0,3,0,0,0,2, +0,1,2,0,0,0,1,2,0,0,0,0,0,0,0,2,2,0,1,0,0,2,0,0,0,2,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,3,3,2,2,0,0,0,2,0,2,3,3,0,2,0,0,0,0,0,0,2,2,2,0,2,2,0,2,0,2, +0,2,2,0,0,2,2,2,2,1,0,0,2,2,0,2,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0, +0,2,0,3,2,3,0,0,0,3,0,0,2,2,0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,0,2, +0,0,2,2,0,0,2,2,2,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,2,0,0,3,2,0,2,2,2,2,2,0,0,0,2,0,0,0,0,2,0,1,0,0,2,0,1,0,0,0, +0,2,2,2,0,2,2,0,1,2,0,2,2,2,0,2,2,2,2,1,2,2,0,0,2,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,2,0,2,0,2,2,0,0,0,0,1,2,1,0,0,2,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,3,2,3,0,0,2,0,0,0,2,2,0,2,0,0,0,1,0,0,2,0,2,0,2,2,0,0,0,0, +0,0,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0, +0,2,2,3,2,2,0,0,0,0,0,0,1,3,0,2,0,2,2,0,0,0,1,0,2,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,0,2,0,3,2,0,2,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +0,0,2,0,0,0,0,1,1,0,0,2,1,2,0,2,2,0,1,0,0,1,0,0,0,2,0,0,0,0,0,0, +0,3,0,2,2,2,0,0,2,0,0,0,2,0,0,0,2,3,0,2,0,0,0,0,0,0,2,2,0,0,0,2, +0,1,2,0,0,0,1,2,2,1,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,1,2,0,2,2,0,2,0,0,2,0,0,0,0,1,2,1,0,2,1,0,0,0,0,0,0,0,0,0,0, +0,0,2,0,0,0,3,1,2,2,0,2,0,0,0,0,2,0,0,0,2,0,0,3,0,0,0,0,2,2,2,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,1,0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,2, +0,2,2,0,0,2,2,2,2,2,0,1,2,0,0,0,2,2,0,1,0,2,0,0,2,2,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,2, +0,1,2,0,0,0,0,2,2,1,0,1,0,1,0,2,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,2,0,0,2,2,0,0,0,0,1,0,0,0,0,0,0,2, +0,2,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0, +0,2,2,2,2,0,0,0,3,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,1, +0,0,2,0,0,0,0,1,2,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,2,2,2,0,0,0,2,0,0,0,0,0,0,0,0,2, +0,0,1,0,0,0,0,2,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, +0,3,0,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,2, +0,0,2,0,0,0,0,2,2,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,0,2,2,1,0,0,0,0,0,0,2,0,0,2,0,2,2,2,0,0,0,0,0,0,2,0,0,0,0,2, +0,0,2,0,0,2,0,2,2,0,0,0,0,2,0,2,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0, +0,0,3,0,0,0,2,2,0,2,2,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0, +0,2,2,2,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1, +0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,2,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,2,0,0,0,2,0,0,0,0,0,1,0,0,0,0,2,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,2,0,0,0, +0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,2,0,2,0,0,0, +0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,2,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +) + +Latin7GreekModel = { + 'char_to_order_map': Latin7_char_to_order_map, + 'precedence_matrix': GreekLangModel, + 'typical_positive_ratio': 0.982851, + 'keep_english_letter': False, + 'charset_name': "ISO-8859-7", + 'language': 'Greek', +} + +Win1253GreekModel = { + 'char_to_order_map': win1253_char_to_order_map, + 'precedence_matrix': GreekLangModel, + 'typical_positive_ratio': 0.982851, + 'keep_english_letter': False, + 'charset_name': "windows-1253", + 'language': 'Greek', +} diff --git a/robot/lib/python3.8/site-packages/chardet/langhebrewmodel.py b/robot/lib/python3.8/site-packages/chardet/langhebrewmodel.py new file mode 100644 index 0000000000000000000000000000000000000000..58f4c875ec926b85256fd3866369fc8a81a14350 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/langhebrewmodel.py @@ -0,0 +1,200 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Simon Montagu +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Shoshannah Forbes - original C code (?) +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Windows-1255 language model +# Character Mapping Table: +WIN1255_CHAR_TO_ORDER_MAP = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 69, 91, 79, 80, 92, 89, 97, 90, 68,111,112, 82, 73, 95, 85, # 40 + 78,121, 86, 71, 67,102,107, 84,114,103,115,253,253,253,253,253, # 50 +253, 50, 74, 60, 61, 42, 76, 70, 64, 53,105, 93, 56, 65, 54, 49, # 60 + 66,110, 51, 43, 44, 63, 81, 77, 98, 75,108,253,253,253,253,253, # 70 +124,202,203,204,205, 40, 58,206,207,208,209,210,211,212,213,214, +215, 83, 52, 47, 46, 72, 32, 94,216,113,217,109,218,219,220,221, + 34,116,222,118,100,223,224,117,119,104,125,225,226, 87, 99,227, +106,122,123,228, 55,229,230,101,231,232,120,233, 48, 39, 57,234, + 30, 59, 41, 88, 33, 37, 36, 31, 29, 35,235, 62, 28,236,126,237, +238, 38, 45,239,240,241,242,243,127,244,245,246,247,248,249,250, + 9, 8, 20, 16, 3, 2, 24, 14, 22, 1, 25, 15, 4, 11, 6, 23, + 12, 19, 13, 26, 18, 27, 21, 17, 7, 10, 5,251,252,128, 96,253, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 98.4004% +# first 1024 sequences: 1.5981% +# rest sequences: 0.087% +# negative sequences: 0.0015% +HEBREW_LANG_MODEL = ( +0,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,3,2,1,2,0,1,0,0, +3,0,3,1,0,0,1,3,2,0,1,1,2,0,2,2,2,1,1,1,1,2,1,1,1,2,0,0,2,2,0,1, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2, +1,2,1,2,1,2,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2, +1,2,1,3,1,1,0,0,2,0,0,0,1,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,1,2,2,1,3, +1,2,1,1,2,2,0,0,2,2,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,1,0,1,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,2,2,2,3,2, +1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,3,2,2,3,2,2,2,1,2,2,2,2, +1,2,1,1,2,2,0,1,2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,0,2,2,2,2,2, +0,2,0,2,2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,0,2,2,2, +0,2,1,2,2,2,0,0,2,1,0,0,0,0,1,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,2,1,2,3,2,2,2, +1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,2,0,2, +0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,2,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,2,2,3,2,1,2,1,1,1, +0,1,1,1,1,1,3,0,1,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,0,1,0,0,1,0,0,0,0, +0,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2, +0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,2,1,2,3,3,2,3,3,3,3,2,3,2,1,2,0,2,1,2, +0,2,0,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,1,2,2,3,3,2,3,2,3,2,2,3,1,2,2,0,2,2,2, +0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,2,2,3,3,3,3,1,3,2,2,2, +0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,2,3,2,2,2,1,2,2,0,2,2,2,2, +0,2,0,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,1,3,2,3,3,2,3,3,2,2,1,2,2,2,2,2,2, +0,2,1,2,1,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,2,3,3,2,3,3,3,3,2,3,2,3,3,3,3,3,2,2,2,2,2,2,2,1, +0,2,0,1,2,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,2,1,2,3,3,3,3,3,3,3,2,3,2,3,2,1,2,3,0,2,1,2,2, +0,2,1,1,2,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,2,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,3,2,1,3,1,2,2,2,1,2,3,3,1,2,1,2,2,2,2, +0,1,1,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,0,2,3,3,3,1,3,3,3,1,2,2,2,2,1,1,2,2,2,2,2,2, +0,2,0,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,3,3,2,2,3,3,3,2,1,2,3,2,3,2,2,2,2,1,2,1,1,1,2,2, +0,2,1,1,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,0,0,0, +1,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,2,3,3,2,3,1,2,2,2,2,3,2,3,1,1,2,2,1,2,2,1,1,0,2,2,2,2, +0,1,0,1,2,2,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,0,0,1,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,0, +0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,1,0,1,0,1,1,0,1,1,0,0,0,1,1,0,1,1,1,0,0,0,0,0,0,1,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,0,0,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +3,2,2,1,2,2,2,2,2,2,2,1,2,2,1,2,2,1,1,1,1,1,1,1,1,2,1,1,0,3,3,3, +0,3,0,2,2,2,2,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +2,2,2,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,1,1,1,2,0,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,0,0,0,0,0,0, +0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,1,0,2,1,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +0,3,1,1,2,2,2,2,2,1,2,2,2,1,1,2,2,2,2,2,2,2,1,2,2,1,0,1,1,1,1,0, +0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,1,1,1,1,2,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, +0,0,2,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,1,0,0, +2,1,1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,1,2,1,2,1,1,1,1,0,0,0,0, +0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,2,1,2,2,2,2,2,2,2,2,2,2,1,2,1,2,1,1,2,1,1,1,2,1,2,1,2,0,1,0,1, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,1,2,2,2,1,2,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,2,1,2,1,1,0,1,0,1, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,1,2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2, +0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,1,1,1,1,1,1,1,0,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,2,0,1,1,1,0,1,0,0,0,1,1,0,1,1,0,0,0,0,0,1,1,0,0, +0,1,1,1,2,1,2,2,2,0,2,0,2,0,1,1,2,1,1,1,1,2,1,0,1,1,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,1,0,0,0,0,0,1,0,1,2,2,0,1,0,0,1,1,2,2,1,2,0,2,0,0,0,1,2,0,1, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,2,0,2,1,2,0,2,0,0,1,1,1,1,1,1,0,1,0,0,0,1,0,0,1, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,0,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,1,2,2,0,0,1,0,0,0,1,0,0,1, +1,1,2,1,0,1,1,1,0,1,0,1,1,1,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,2,1, +0,2,0,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,1,0,0,1,0,1,1,1,1,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,1,0,0,0,1,1,0,1, +2,0,1,0,1,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,1,1,1,0,1,0,0,1,1,2,1,1,2,0,1,0,0,0,1,1,0,1, +1,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,0,0,2,1,1,2,0,2,0,0,0,1,1,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,2,1,1,0,1,0,0,2,2,1,2,1,1,0,1,0,0,0,1,1,0,1, +2,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,2,2,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,1,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,2,2,0,0,0,0,2,1,1,1,0,2,1,1,0,0,0,2,1,0,1, +1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,1,1,0,2,1,1,0,1,0,0,0,1,1,0,1, +2,2,1,1,1,0,1,1,0,1,1,0,1,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,0,1,2,1,0,2,0,0,0,1,1,0,1, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0, +0,1,0,0,2,0,2,1,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,1,1,1,0,1,0,0,1,0,0,0,1,0,0,1, +1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,0,0,0,0,0,1,0,1,1,0,0,1,0,0,2,1,1,1,1,1,0,1,0,0,0,0,1,0,1, +0,1,1,1,2,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,2,1,0,0,0,0,0,1,1,1,1,1,0,1,0,0,0,1,1,0,0, +) + +Win1255HebrewModel = { + 'char_to_order_map': WIN1255_CHAR_TO_ORDER_MAP, + 'precedence_matrix': HEBREW_LANG_MODEL, + 'typical_positive_ratio': 0.984004, + 'keep_english_letter': False, + 'charset_name': "windows-1255", + 'language': 'Hebrew', +} diff --git a/robot/lib/python3.8/site-packages/chardet/langhungarianmodel.py b/robot/lib/python3.8/site-packages/chardet/langhungarianmodel.py new file mode 100644 index 0000000000000000000000000000000000000000..bb7c095e1ea6523bd00365384e4c662954c678a0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/langhungarianmodel.py @@ -0,0 +1,225 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +Latin2_HungarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, + 46, 71, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, +253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, + 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, +159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174, +175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190, +191,192,193,194,195,196,197, 75,198,199,200,201,202,203,204,205, + 79,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, +221, 51, 81,222, 78,223,224,225,226, 44,227,228,229, 61,230,231, +232,233,234, 58,235, 66, 59,236,237,238, 60, 69, 63,239,240,241, + 82, 14, 74,242, 70, 80,243, 72,244, 15, 83, 77, 84, 30, 76, 85, +245,246,247, 25, 73, 42, 24,248,249,250, 31, 56, 29,251,252,253, +) + +win1250HungarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, + 46, 72, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, +253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, + 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, +161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176, +177,178,179,180, 78,181, 69,182,183,184,185,186,187,188,189,190, +191,192,193,194,195,196,197, 76,198,199,200,201,202,203,204,205, + 81,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, +221, 51, 83,222, 80,223,224,225,226, 44,227,228,229, 61,230,231, +232,233,234, 58,235, 66, 59,236,237,238, 60, 70, 63,239,240,241, + 84, 14, 75,242, 71, 82,243, 73,244, 15, 85, 79, 86, 30, 77, 87, +245,246,247, 25, 74, 42, 24,248,249,250, 31, 56, 29,251,252,253, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 94.7368% +# first 1024 sequences:5.2623% +# rest sequences: 0.8894% +# negative sequences: 0.0009% +HungarianLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, +3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,2,3,3,1,1,2,2,2,2,2,1,2, +3,2,2,3,3,3,3,3,2,3,3,3,3,3,3,1,2,3,3,3,3,2,3,3,1,1,3,3,0,1,1,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0, +3,2,1,3,3,3,3,3,2,3,3,3,3,3,1,1,2,3,3,3,3,3,3,3,1,1,3,2,0,1,1,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,1,1,2,3,3,3,1,3,3,3,3,3,1,3,3,2,2,0,3,2,3, +0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,3,3,2,3,3,2,2,3,2,3,2,0,3,2,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,3,3,3,3,2,3,3,3,1,2,3,2,2,3,1,2,3,3,2,2,0,3,3,3, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,3,2,3,3,3,3,2,3,3,3,3,0,2,3,2, +0,0,0,1,1,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,1,1,1,3,3,2,1,3,2,2,3,2,1,3,2,2,1,0,3,3,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,2,2,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,3,2,2,3,1,1,3,2,0,1,1,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,1,3,3,3,3,3,2,2,1,3,3,3,0,1,1,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,2,0,3,2,3, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,1,3,2,2,2,3,1,1,3,3,1,1,0,3,3,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,2,3,3,3,3,3,1,2,3,2,2,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,2,2,2,3,1,3,3,2,2,1,3,3,3,1,1,3,1,2,3,2,3,2,2,2,1,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,2,2,3,2,1,0,3,2,0,1,1,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,1,0,3,3,3,3,0,2,3,0,0,2,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,2,3,3,2,2,2,2,3,3,0,1,2,3,2,3,2,2,3,2,1,2,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +3,3,3,3,3,3,1,2,3,3,3,2,1,2,3,3,2,2,2,3,2,3,3,1,3,3,1,1,0,2,3,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,1,2,2,2,2,3,3,3,1,1,1,3,3,1,1,3,1,1,3,2,1,2,3,1,1,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,2,1,2,1,1,3,3,1,1,1,1,3,3,1,1,2,2,1,2,1,1,2,2,1,1,0,2,2,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,1,1,2,1,1,3,3,1,0,1,1,3,3,2,0,1,1,2,3,1,0,2,2,1,0,0,1,3,2, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,2,1,3,3,3,3,3,1,2,3,2,3,3,2,1,1,3,2,3,2,1,2,2,0,1,2,1,0,0,1,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,2,2,2,2,3,1,2,2,1,1,3,3,0,3,2,1,2,3,2,1,3,3,1,1,0,2,1,3, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,2,2,2,3,2,3,3,3,2,1,1,3,3,1,1,1,2,2,3,2,3,2,2,2,1,0,2,2,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +1,0,0,3,3,3,3,3,0,0,3,3,2,3,0,0,0,2,3,3,1,0,1,2,0,0,1,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,2,3,3,3,3,3,1,2,3,3,2,2,1,1,0,3,3,2,2,1,2,2,1,0,2,2,0,1,1,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,2,2,1,3,1,2,3,3,2,2,1,1,2,2,1,1,1,1,3,2,1,1,1,1,2,1,0,1,2,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +2,3,3,1,1,1,1,1,3,3,3,0,1,1,3,3,1,1,1,1,1,2,2,0,3,1,1,2,0,2,1,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,1,0,1,2,1,2,2,0,1,2,3,1,2,0,0,0,2,1,1,1,1,1,2,0,0,1,1,0,0,0,0, +1,2,1,2,2,2,1,2,1,2,0,2,0,2,2,1,1,2,1,1,2,1,1,1,0,1,0,0,0,1,1,0, +1,1,1,2,3,2,3,3,0,1,2,2,3,1,0,1,0,2,1,2,2,0,1,1,0,0,1,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,3,3,2,2,1,0,0,3,2,3,2,0,0,0,1,1,3,0,0,1,1,0,0,2,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,1,2,2,3,3,1,0,1,3,2,3,1,1,1,0,1,1,1,1,1,3,1,0,0,2,2,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,1,1,2,2,2,1,0,1,2,3,3,2,0,0,0,2,1,1,1,2,1,1,1,0,1,1,1,0,0,0, +1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,2,1,1,1,1,1,1,0,1,1,1,0,0,1,1, +3,2,2,1,0,0,1,1,2,2,0,3,0,1,2,1,1,0,0,1,1,1,0,1,1,1,1,0,2,1,1,1, +2,2,1,1,1,2,1,2,1,1,1,1,1,1,1,2,1,1,1,2,3,1,1,1,1,1,1,1,1,1,0,1, +2,3,3,0,1,0,0,0,3,3,1,0,0,1,2,2,1,0,0,0,0,2,0,0,1,1,1,0,2,1,1,1, +2,1,1,1,1,1,1,2,1,1,0,1,1,0,1,1,1,0,1,2,1,1,0,1,1,1,1,1,1,1,0,1, +2,3,3,0,1,0,0,0,2,2,0,0,0,0,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,0,1,0, +2,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, +3,2,2,0,1,0,1,0,2,3,2,0,0,1,2,2,1,0,0,1,1,1,0,0,2,1,0,1,2,2,1,1, +2,1,1,1,1,1,1,2,1,1,1,1,1,1,0,2,1,0,1,1,0,1,1,1,0,1,1,2,1,1,0,1, +2,2,2,0,0,1,0,0,2,2,1,1,0,0,2,1,1,0,0,0,1,2,0,0,2,1,0,0,2,1,1,1, +2,1,1,1,1,2,1,2,1,1,1,2,2,1,1,2,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1, +1,2,3,0,0,0,1,0,3,2,1,0,0,1,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,2,1, +1,1,0,0,0,1,0,1,1,1,1,1,2,0,0,1,0,0,0,2,0,0,1,1,1,1,1,1,1,1,0,1, +3,0,0,2,1,2,2,1,0,0,2,1,2,2,0,0,0,2,1,1,1,0,1,1,0,0,1,1,2,0,0,0, +1,2,1,2,2,1,1,2,1,2,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,0,0,1, +1,3,2,0,0,0,1,0,2,2,2,0,0,0,2,2,1,0,0,0,0,3,1,1,1,1,0,0,2,1,1,1, +2,1,0,1,1,1,0,1,1,1,1,1,1,1,0,2,1,0,0,1,0,1,1,0,1,1,1,1,1,1,0,1, +2,3,2,0,0,0,1,0,2,2,0,0,0,0,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,1,0, +2,1,1,1,1,2,1,2,1,2,0,1,1,1,0,2,1,1,1,2,1,1,1,1,0,1,1,1,1,1,0,1, +3,1,1,2,2,2,3,2,1,1,2,2,1,1,0,1,0,2,2,1,1,1,1,1,0,0,1,1,0,1,1,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,0,0,0,0,0,2,2,0,0,0,0,2,2,1,0,0,0,1,1,0,0,1,2,0,0,2,1,1,1, +2,2,1,1,1,2,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,1,1,0,1,2,1,1,1,0,1, +1,0,0,1,2,3,2,1,0,0,2,0,1,1,0,0,0,1,1,1,1,0,1,1,0,0,1,0,0,0,0,0, +1,2,1,2,1,2,1,1,1,2,0,2,1,1,1,0,1,2,0,0,1,1,1,0,0,0,0,0,0,0,0,0, +2,3,2,0,0,0,0,0,1,1,2,1,0,0,1,1,1,0,0,0,0,2,0,0,1,1,0,0,2,1,1,1, +2,1,1,1,1,1,1,2,1,0,1,1,1,1,0,2,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1, +1,2,2,0,1,1,1,0,2,2,2,0,0,0,3,2,1,0,0,0,1,1,0,0,1,1,0,1,1,1,0,0, +1,1,0,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,0,0,1,1,1,0,1,0,1, +2,1,0,2,1,1,2,2,1,1,2,1,1,1,0,0,0,1,1,0,1,1,1,1,0,0,1,1,1,0,0,0, +1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,1,0, +1,2,3,0,0,0,1,0,2,2,0,0,0,0,2,2,0,0,0,0,0,1,0,0,1,0,0,0,2,0,1,0, +2,1,1,1,1,1,0,2,0,0,0,1,2,1,1,1,1,0,1,2,0,1,0,1,0,1,1,1,0,1,0,1, +2,2,2,0,0,0,1,0,2,1,2,0,0,0,1,1,2,0,0,0,0,1,0,0,1,1,0,0,2,1,0,1, +2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, +1,2,2,0,0,0,1,0,2,2,2,0,0,0,1,1,0,0,0,0,0,1,1,0,2,0,0,1,1,1,0,1, +1,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,0,0,1,1,0,1,0,1,1,1,1,1,0,0,0,1, +1,0,0,1,0,1,2,1,0,0,1,1,1,2,0,0,0,1,1,0,1,0,1,1,0,0,1,0,0,0,0,0, +0,2,1,2,1,1,1,1,1,2,0,2,0,1,1,0,1,2,1,0,1,1,1,0,0,0,0,0,0,1,0,0, +2,1,1,0,1,2,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,2,1,0,1, +2,2,1,1,1,1,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,0,1,0,1,1,1,1,1,0,1, +1,2,2,0,0,0,0,0,1,1,0,0,0,0,2,1,0,0,0,0,0,2,0,0,2,2,0,0,2,0,0,1, +2,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,0,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1, +1,1,2,0,0,3,1,0,2,1,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,1,0,0,1,0,1,0, +1,2,1,0,1,1,1,2,1,1,0,1,1,1,1,1,0,0,0,1,1,1,1,1,0,1,0,0,0,1,0,0, +2,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,2,0,0,0, +2,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,1,0,1, +2,1,1,1,2,1,1,1,0,1,1,2,1,0,0,0,0,1,1,1,1,0,1,0,0,0,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,1,0,1,1,1,1,1,0,0,1,1,2,1,0,0,0,1,1,0,0,0,1,1,0,0,1,0,1,0,0,0, +1,2,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,0,0, +2,0,0,0,1,1,1,1,0,0,1,1,0,0,0,0,0,1,1,1,2,0,0,1,0,0,1,0,1,0,0,0, +0,1,1,1,1,1,1,1,1,2,0,1,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,1,1,0,0,2,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0, +0,1,1,1,1,1,1,0,1,1,0,1,0,1,1,0,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +0,1,1,1,1,1,0,0,1,1,0,1,0,1,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, +0,0,0,1,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,1,1,1,0,1,0,0,1,1,0,1,0,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, +2,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,0,1,0,0,1,0,1,0,1,1,1,0,0,1,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,1,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0, +0,1,1,1,1,1,1,0,1,1,0,1,0,1,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0, +) + +Latin2HungarianModel = { + 'char_to_order_map': Latin2_HungarianCharToOrderMap, + 'precedence_matrix': HungarianLangModel, + 'typical_positive_ratio': 0.947368, + 'keep_english_letter': True, + 'charset_name': "ISO-8859-2", + 'language': 'Hungarian', +} + +Win1250HungarianModel = { + 'char_to_order_map': win1250HungarianCharToOrderMap, + 'precedence_matrix': HungarianLangModel, + 'typical_positive_ratio': 0.947368, + 'keep_english_letter': True, + 'charset_name': "windows-1250", + 'language': 'Hungarian', +} diff --git a/robot/lib/python3.8/site-packages/chardet/langthaimodel.py b/robot/lib/python3.8/site-packages/chardet/langthaimodel.py new file mode 100644 index 0000000000000000000000000000000000000000..15f94c2df021c9cccc761ebeec80146edbb000c9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/langthaimodel.py @@ -0,0 +1,199 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# The following result for thai was collected from a limited sample (1M). + +# Character Mapping Table: +TIS620CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,182,106,107,100,183,184,185,101, 94,186,187,108,109,110,111, # 40 +188,189,190, 89, 95,112,113,191,192,193,194,253,253,253,253,253, # 50 +253, 64, 72, 73,114, 74,115,116,102, 81,201,117, 90,103, 78, 82, # 60 + 96,202, 91, 79, 84,104,105, 97, 98, 92,203,253,253,253,253,253, # 70 +209,210,211,212,213, 88,214,215,216,217,218,219,220,118,221,222, +223,224, 99, 85, 83,225,226,227,228,229,230,231,232,233,234,235, +236, 5, 30,237, 24,238, 75, 8, 26, 52, 34, 51,119, 47, 58, 57, + 49, 53, 55, 43, 20, 19, 44, 14, 48, 3, 17, 25, 39, 62, 31, 54, + 45, 9, 16, 2, 61, 15,239, 12, 42, 46, 18, 21, 76, 4, 66, 63, + 22, 10, 1, 36, 23, 13, 40, 27, 32, 35, 86,240,241,242,243,244, + 11, 28, 41, 29, 33,245, 50, 37, 6, 7, 67, 77, 38, 93,246,247, + 68, 56, 59, 65, 69, 60, 70, 80, 71, 87,248,249,250,251,252,253, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 92.6386% +# first 1024 sequences:7.3177% +# rest sequences: 1.0230% +# negative sequences: 0.0436% +ThaiLangModel = ( +0,1,3,3,3,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,0,0,3,3,3,0,3,3,3,3, +0,3,3,0,0,0,1,3,0,3,3,2,3,3,0,1,2,3,3,3,3,0,2,0,2,0,0,3,2,1,2,2, +3,0,3,3,2,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,0,3,2,3,0,2,2,2,3, +0,2,3,0,0,0,0,1,0,1,2,3,1,1,3,2,2,0,1,1,0,0,1,0,0,0,0,0,0,0,1,1, +3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,3,3,2,3,2,3,3,2,2,2, +3,1,2,3,0,3,3,2,2,1,2,3,3,1,2,0,1,3,0,1,0,0,1,0,0,0,0,0,0,0,1,1, +3,3,2,2,3,3,3,3,1,2,3,3,3,3,3,2,2,2,2,3,3,2,2,3,3,2,2,3,2,3,2,2, +3,3,1,2,3,1,2,2,3,3,1,0,2,1,0,0,3,1,2,1,0,0,1,0,0,0,0,0,0,1,0,1, +3,3,3,3,3,3,2,2,3,3,3,3,2,3,2,2,3,3,2,2,3,2,2,2,2,1,1,3,1,2,1,1, +3,2,1,0,2,1,0,1,0,1,1,0,1,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0, +3,3,3,2,3,2,3,3,2,2,3,2,3,3,2,3,1,1,2,3,2,2,2,3,2,2,2,2,2,1,2,1, +2,2,1,1,3,3,2,1,0,1,2,2,0,1,3,0,0,0,1,1,0,0,0,0,0,2,3,0,0,2,1,1, +3,3,2,3,3,2,0,0,3,3,0,3,3,0,2,2,3,1,2,2,1,1,1,0,2,2,2,0,2,2,1,1, +0,2,1,0,2,0,0,2,0,1,0,0,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,2,3,3,2,0,0,3,3,0,2,3,0,2,1,2,2,2,2,1,2,0,0,2,2,2,0,2,2,1,1, +0,2,1,0,2,0,0,2,0,1,1,0,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0, +3,3,2,3,2,3,2,0,2,2,1,3,2,1,3,2,1,2,3,2,2,3,0,2,3,2,2,1,2,2,2,2, +1,2,2,0,0,0,0,2,0,1,2,0,1,1,1,0,1,0,3,1,1,0,0,0,0,0,0,0,0,0,1,0, +3,3,2,3,3,2,3,2,2,2,3,2,2,3,2,2,1,2,3,2,2,3,1,3,2,2,2,3,2,2,2,3, +3,2,1,3,0,1,1,1,0,2,1,1,1,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,0,2,0,0, +1,0,0,3,0,3,3,3,3,3,0,0,3,0,2,2,3,3,3,3,3,0,0,0,1,1,3,0,0,0,0,2, +0,0,1,0,0,0,0,0,0,0,2,3,0,0,0,3,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0, +2,0,3,3,3,3,0,0,2,3,0,0,3,0,3,3,2,3,3,3,3,3,0,0,3,3,3,0,0,0,3,3, +0,0,3,0,0,0,0,2,0,0,2,1,1,3,0,0,1,0,0,2,3,0,1,0,0,0,0,0,0,0,1,0, +3,3,3,3,2,3,3,3,3,3,3,3,1,2,1,3,3,2,2,1,2,2,2,3,1,1,2,0,2,1,2,1, +2,2,1,0,0,0,1,1,0,1,0,1,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0, +3,0,2,1,2,3,3,3,0,2,0,2,2,0,2,1,3,2,2,1,2,1,0,0,2,2,1,0,2,1,2,2, +0,1,1,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,1,3,3,1,1,3,0,2,3,1,1,3,2,1,1,2,0,2,2,3,2,1,1,1,1,1,2, +3,0,0,1,3,1,2,1,2,0,3,0,0,0,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, +3,3,1,1,3,2,3,3,3,1,3,2,1,3,2,1,3,2,2,2,2,1,3,3,1,2,1,3,1,2,3,0, +2,1,1,3,2,2,2,1,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +3,3,2,3,2,3,3,2,3,2,3,2,3,3,2,1,0,3,2,2,2,1,2,2,2,1,2,2,1,2,1,1, +2,2,2,3,0,1,3,1,1,1,1,0,1,1,0,2,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,3,2,2,1,1,3,2,3,2,3,2,0,3,2,2,1,2,0,2,2,2,1,2,2,2,2,1, +3,2,1,2,2,1,0,2,0,1,0,0,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,2,3,1,2,3,3,2,2,3,0,1,1,2,0,3,3,2,2,3,0,1,1,3,0,0,0,0, +3,1,0,3,3,0,2,0,2,1,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,2,3,2,3,3,0,1,3,1,1,2,1,2,1,1,3,1,1,0,2,3,1,1,1,1,1,1,1,1, +3,1,1,2,2,2,2,1,1,1,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,2,2,1,1,2,1,3,3,2,3,2,2,3,2,2,3,1,2,2,1,2,0,3,2,1,2,2,2,2,2,1, +3,2,1,2,2,2,1,1,1,1,0,0,1,1,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,1,3,3,0,2,1,0,3,2,0,0,3,1,0,1,1,0,1,0,0,0,0,0,1, +1,0,0,1,0,3,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,2,2,2,3,0,0,1,3,0,3,2,0,3,2,2,3,3,3,3,3,1,0,2,2,2,0,2,2,1,2, +0,2,3,0,0,0,0,1,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,0,2,3,1,3,3,2,3,3,0,3,3,0,3,2,2,3,2,3,3,3,0,0,2,2,3,0,1,1,1,3, +0,0,3,0,0,0,2,2,0,1,3,0,1,2,2,2,3,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1, +3,2,3,3,2,0,3,3,2,2,3,1,3,2,1,3,2,0,1,2,2,0,2,3,2,1,0,3,0,0,0,0, +3,0,0,2,3,1,3,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,3,2,2,2,1,2,0,1,3,1,1,3,1,3,0,0,2,1,1,1,1,2,1,1,1,0,2,1,0,1, +1,2,0,0,0,3,1,1,0,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,0,3,1,0,0,0,1,0, +3,3,3,3,2,2,2,2,2,1,3,1,1,1,2,0,1,1,2,1,2,1,3,2,0,0,3,1,1,1,1,1, +3,1,0,2,3,0,0,0,3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,2,3,0,3,3,0,2,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,2,3,1,3,0,0,1,2,0,0,2,0,3,3,2,3,3,3,2,3,0,0,2,2,2,0,0,0,2,2, +0,0,1,0,0,0,0,3,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +0,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,1,2,3,1,3,3,0,0,1,0,3,0,0,0,0,0, +0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,1,2,3,1,2,3,1,0,3,0,2,2,1,0,2,1,1,2,0,1,0,0,1,1,1,1,0,1,0,0, +1,0,0,0,0,1,1,0,3,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,1,0,1,1,1,3,1,2,2,2,2,2,2,1,1,1,1,0,3,1,0,1,3,1,1,1,1, +1,1,0,2,0,1,3,1,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1, +3,0,2,2,1,3,3,2,3,3,0,1,1,0,2,2,1,2,1,3,3,1,0,0,3,2,0,0,0,0,2,1, +0,1,0,0,0,0,1,2,0,1,1,3,1,1,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,0,3,0,0,1,0,0,0,3,0,0,3,0,3,1,0,1,1,1,3,2,0,0,0,3,0,0,0,0,2,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,1,3,2,1,3,3,1,2,2,0,1,2,1,0,1,2,0,0,0,0,0,3,0,0,0,3,0,0,0,0, +3,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,1,2,0,3,3,3,2,2,0,1,1,0,1,3,0,0,0,2,2,0,0,0,0,3,1,0,1,0,0,0, +0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,2,3,1,2,0,0,2,1,0,3,1,0,1,2,0,1,1,1,1,3,0,0,3,1,1,0,2,2,1,1, +0,2,0,0,0,0,0,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,0,3,1,2,0,0,2,2,0,1,2,0,1,0,1,3,1,2,1,0,0,0,2,0,3,0,0,0,1,0, +0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,1,1,2,2,0,0,0,2,0,2,1,0,1,1,0,1,1,1,2,1,0,0,1,1,1,0,2,1,1,1, +0,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,1, +0,0,0,2,0,1,3,1,1,1,1,0,0,0,0,3,2,0,1,0,0,0,1,2,0,0,0,1,0,0,0,0, +0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,2,3,2,2,0,0,0,1,0,0,0,0,2,3,2,1,2,2,3,0,0,0,2,3,1,0,0,0,1,1, +0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0, +3,3,2,2,0,1,0,0,0,0,2,0,2,0,1,0,0,0,1,1,0,0,0,2,1,0,1,0,1,1,0,0, +0,1,0,2,0,0,1,0,3,0,1,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,1,0,0,1,0,0,0,0,0,1,1,2,0,0,0,0,1,0,0,1,3,1,0,0,0,0,1,1,0,0, +0,1,0,0,0,0,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0, +3,3,1,1,1,1,2,3,0,0,2,1,1,1,1,1,0,2,1,1,0,0,0,2,1,0,1,2,1,1,0,1, +2,1,0,3,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,3,1,0,0,0,0,0,0,0,3,0,0,0,3,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1, +0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,2,0,0,0,0,0,0,1,2,1,0,1,1,0,2,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,2,0,0,0,1,3,0,1,0,0,0,2,0,0,0,0,0,0,0,1,2,0,0,0,0,0, +3,3,0,0,1,1,2,0,0,1,2,1,0,1,1,1,0,1,1,0,0,2,1,1,0,1,0,0,1,1,1,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,1,0,0,0,0,1,0,0,0,0,3,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, +2,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,0,0,1,1,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,1,0,1,2,0,1,2,0,0,1,1,0,2,0,1,0,0,1,0,0,0,0,1,0,0,0,2,0,0,0,0, +1,0,0,1,0,1,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,1,0,0,0,0,0,0,0,1,1,0,1,1,0,2,1,3,0,0,0,0,1,1,0,0,0,0,0,0,0,3, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,1,0,1,0,0,2,0,0,2,0,0,1,1,2,0,0,1,1,0,0,0,1,0,0,0,1,1,0,0,0, +1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,3,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0, +1,0,0,0,0,0,0,0,0,1,0,0,0,0,2,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,1,0,0,2,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +) + +TIS620ThaiModel = { + 'char_to_order_map': TIS620CharToOrderMap, + 'precedence_matrix': ThaiLangModel, + 'typical_positive_ratio': 0.926386, + 'keep_english_letter': False, + 'charset_name': "TIS-620", + 'language': 'Thai', +} diff --git a/robot/lib/python3.8/site-packages/chardet/langturkishmodel.py b/robot/lib/python3.8/site-packages/chardet/langturkishmodel.py new file mode 100644 index 0000000000000000000000000000000000000000..a427a457398de8076cdcefb5a6c391e89500bce8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/langturkishmodel.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Özgür Baskın - Turkish Language Model +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +Latin5_TurkishCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255, 23, 37, 47, 39, 29, 52, 36, 45, 53, 60, 16, 49, 20, 46, 42, + 48, 69, 44, 35, 31, 51, 38, 62, 65, 43, 56,255,255,255,255,255, +255, 1, 21, 28, 12, 2, 18, 27, 25, 3, 24, 10, 5, 13, 4, 15, + 26, 64, 7, 8, 9, 14, 32, 57, 58, 11, 22,255,255,255,255,255, +180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165, +164,163,162,161,160,159,101,158,157,156,155,154,153,152,151,106, +150,149,148,147,146,145,144,100,143,142,141,140,139,138,137,136, + 94, 80, 93,135,105,134,133, 63,132,131,130,129,128,127,126,125, +124,104, 73, 99, 79, 85,123, 54,122, 98, 92,121,120, 91,103,119, + 68,118,117, 97,116,115, 50, 90,114,113,112,111, 55, 41, 40, 86, + 89, 70, 59, 78, 71, 82, 88, 33, 77, 66, 84, 83,110, 75, 61, 96, + 30, 67,109, 74, 87,102, 34, 95, 81,108, 76, 72, 17, 6, 19,107, +) + +TurkishLangModel = ( +3,2,3,3,3,1,3,3,3,3,3,3,3,3,2,1,1,3,3,1,3,3,0,3,3,3,3,3,0,3,1,3, +3,2,1,0,0,1,1,0,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,2,2,0,0,1,0,0,1, +3,2,2,3,3,0,3,3,3,3,3,3,3,2,3,1,0,3,3,1,3,3,0,3,3,3,3,3,0,3,0,3, +3,1,1,0,1,0,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,2,2,0,0,0,1,0,1, +3,3,2,3,3,0,3,3,3,3,3,3,3,2,3,1,1,3,3,0,3,3,1,2,3,3,3,3,0,3,0,3, +3,1,1,0,0,0,1,0,0,0,0,1,1,0,1,2,1,0,0,0,1,0,0,0,0,2,0,0,0,0,0,1, +3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,1,3,3,2,0,3,2,1,2,2,1,3,3,0,0,0,2, +2,2,0,1,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,0,1, +3,3,3,2,3,3,1,2,3,3,3,3,3,3,3,1,3,2,1,0,3,2,0,1,2,3,3,2,1,0,0,2, +2,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0, +1,0,1,3,3,1,3,3,3,3,3,3,3,1,2,0,0,2,3,0,2,3,0,0,2,2,2,3,0,3,0,1, +2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,0,3,2,0,2,3,2,3,3,1,0,0,2, +3,2,0,0,1,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,2,0,0,1, +3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,0,3,3,0,0,2,1,0,0,2,3,2,2,0,0,0,2, +2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,0,1,0,2,0,0,1, +3,3,3,2,3,3,3,3,3,3,3,2,3,3,3,0,3,2,0,1,3,2,1,1,3,2,3,2,1,0,0,2, +2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0, +3,3,3,2,3,3,3,3,3,3,3,2,3,3,3,0,3,2,2,0,2,3,0,0,2,2,2,2,0,0,0,2, +3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,1,0,0,0, +3,3,3,3,3,3,3,2,2,2,2,3,2,3,3,0,3,3,1,1,2,2,0,0,2,2,3,2,0,0,1,3, +0,3,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,1, +3,3,3,2,3,3,3,2,1,2,2,3,2,3,3,0,3,2,0,0,1,1,0,1,1,2,1,2,0,0,0,1, +0,3,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0, +3,3,3,2,3,3,2,3,2,2,2,3,3,3,3,1,3,1,1,0,3,2,1,1,3,3,2,3,1,0,0,1, +1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,0,1, +3,2,2,3,3,0,3,3,3,3,3,3,3,2,2,1,0,3,3,1,3,3,0,1,3,3,2,3,0,3,0,3, +2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, +2,2,2,3,3,0,3,3,3,3,3,3,3,3,3,0,0,3,2,0,3,3,0,3,2,3,3,3,0,3,1,3, +2,0,0,0,0,0,0,0,0,0,0,1,0,1,2,0,1,0,0,0,0,0,0,0,2,2,0,0,1,0,0,1, +3,3,3,1,2,3,3,1,0,0,1,0,0,3,3,2,3,0,0,2,0,0,2,0,2,0,0,0,2,0,2,0, +0,3,1,0,1,0,0,0,2,2,1,0,1,1,2,1,2,2,2,0,2,1,1,0,0,0,2,0,0,0,0,0, +1,2,1,3,3,0,3,3,3,3,3,2,3,0,0,0,0,2,3,0,2,3,1,0,2,3,1,3,0,3,0,2, +3,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,1,3,3,2,2,3,2,2,0,1,2,3,0,1,2,1,0,1,0,0,0,1,0,2,2,0,0,0,1, +1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0, +3,3,3,1,3,3,1,1,3,3,1,1,3,3,1,0,2,1,2,0,2,1,0,0,1,1,2,1,0,0,0,2, +2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,1,0,2,1,3,0,0,2,0,0,3,3,0,3,0,0,1,0,1,2,0,0,1,1,2,2,0,1,0, +0,1,2,1,1,0,1,0,1,1,1,1,1,0,1,1,1,2,2,1,2,0,1,0,0,0,0,0,0,1,0,0, +3,3,3,2,3,2,3,3,0,2,2,2,3,3,3,0,3,0,0,0,2,2,0,1,2,1,1,1,0,0,0,1, +0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0, +3,3,3,3,3,3,2,1,2,2,3,3,3,3,2,0,2,0,0,0,2,2,0,0,2,1,3,3,0,0,1,1, +1,1,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0, +1,1,2,3,3,0,3,3,3,3,3,3,2,2,0,2,0,2,3,2,3,2,2,2,2,2,2,2,1,3,2,3, +2,0,2,1,2,2,2,2,1,1,2,2,1,2,2,1,2,0,0,2,1,1,0,2,1,0,0,1,0,0,0,1, +2,3,3,1,1,1,0,1,1,1,2,3,2,1,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0, +0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,2,2,2,3,2,3,2,2,1,3,3,3,0,2,1,2,0,2,1,0,0,1,1,1,1,1,0,0,1, +2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,2,0,1,0,0,0, +3,3,3,2,3,3,3,3,3,2,3,1,2,3,3,1,2,0,0,0,0,0,0,0,3,2,1,1,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, +3,3,3,2,2,3,3,2,1,1,1,1,1,3,3,0,3,1,0,0,1,1,0,0,3,1,2,1,0,0,0,0, +0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0, +3,3,3,2,2,3,2,2,2,3,2,1,1,3,3,0,3,0,0,0,0,1,0,0,3,1,1,2,0,0,0,1, +1,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,1,3,3,0,3,3,3,3,3,2,2,2,1,2,0,2,1,2,2,1,1,0,1,2,2,2,2,2,2,2, +0,0,2,1,2,1,2,1,0,1,1,3,1,2,1,1,2,0,0,2,0,1,0,1,0,1,0,0,0,1,0,1, +3,3,3,1,3,3,3,0,1,1,0,2,2,3,1,0,3,0,0,0,1,0,0,0,1,0,0,1,0,1,0,0, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,2,0,0,2,2,1,0,0,1,0,0,3,3,1,3,0,0,1,1,0,2,0,3,0,0,0,2,0,1,1, +0,1,2,0,1,2,2,0,2,2,2,2,1,0,2,1,1,0,2,0,2,1,2,0,0,0,0,0,0,0,0,0, +3,3,3,1,3,2,3,2,0,2,2,2,1,3,2,0,2,1,2,0,1,2,0,0,1,0,2,2,0,0,0,2, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0, +3,3,3,0,3,3,1,1,2,3,1,0,3,2,3,0,3,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0, +1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,3,3,0,3,3,2,3,3,2,2,0,0,0,0,1,2,0,1,3,0,0,0,3,1,1,0,3,0,2, +2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,1,2,2,1,0,3,1,1,1,1,3,3,2,3,0,0,1,0,1,2,0,2,2,0,2,2,0,2,1, +0,2,2,1,1,1,1,0,2,1,1,0,1,1,1,1,2,1,2,1,2,0,1,0,1,0,0,0,0,0,0,0, +3,3,3,0,1,1,3,0,0,1,1,0,0,2,2,0,3,0,0,1,1,0,1,0,0,0,0,0,2,0,0,0, +0,3,1,0,1,0,1,0,2,0,0,1,0,1,0,1,1,1,2,1,1,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,0,2,0,2,0,1,1,1,0,0,3,3,0,2,0,0,1,0,0,2,1,1,0,1,0,1,0,1,0, +0,2,0,1,2,0,2,0,2,1,1,0,1,0,2,1,1,0,2,1,1,0,1,0,0,0,1,1,0,0,0,0, +3,2,3,0,1,0,0,0,0,0,0,0,0,1,2,0,1,0,0,1,0,0,1,0,0,0,0,0,2,0,0,0, +0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,2,1,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,2,3,0,0,1,0,1,0,2,3,2,3,0,0,1,3,0,2,1,0,0,0,0,2,0,1,0, +0,2,1,0,0,1,1,0,2,1,0,0,1,0,0,1,1,0,1,1,2,0,1,0,0,0,0,1,0,0,0,0, +3,2,2,0,0,1,1,0,0,0,0,0,0,3,1,1,1,0,0,0,0,0,1,0,0,0,0,0,2,0,1,0, +0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,3,3,0,2,3,2,2,1,2,2,1,1,2,0,1,3,2,2,2,0,0,2,2,0,0,0,1,2,1, +3,0,2,1,1,0,1,1,1,0,1,2,2,2,1,1,2,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0, +0,1,1,2,3,0,3,3,3,2,2,2,2,1,0,1,0,1,0,1,2,2,0,0,2,2,1,3,1,1,2,1, +0,0,1,1,2,0,1,1,0,0,1,2,0,2,1,1,2,0,0,1,0,0,0,1,0,1,0,1,0,0,0,0, +3,3,2,0,0,3,1,0,0,0,0,0,0,3,2,1,2,0,0,1,0,0,2,0,0,0,0,0,2,0,1,0, +0,2,1,1,0,0,1,0,1,2,0,0,1,1,0,0,2,1,1,1,1,0,2,0,0,0,0,0,0,0,0,0, +3,3,2,0,0,1,0,0,0,0,1,0,0,3,3,2,2,0,0,1,0,0,2,0,1,0,0,0,2,0,1,0, +0,0,1,1,0,0,2,0,2,1,0,0,1,1,2,1,2,0,2,1,2,1,1,1,0,0,1,1,0,0,0,0, +3,3,2,0,0,2,2,0,0,0,1,1,0,2,2,1,3,1,0,1,0,1,2,0,0,0,0,0,1,0,1,0, +0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,2,0,0,0,1,0,0,1,0,0,2,3,1,2,0,0,1,0,0,2,0,0,0,1,0,2,0,2,0, +0,1,1,2,2,1,2,0,2,1,1,0,0,1,1,0,1,1,1,1,2,1,1,0,0,0,0,0,0,0,0,0, +3,3,3,0,2,1,2,1,0,0,1,1,0,3,3,1,2,0,0,1,0,0,2,0,2,0,1,1,2,0,0,0, +0,0,1,1,1,1,2,0,1,1,0,1,1,1,1,0,0,0,1,1,1,0,1,0,0,0,1,0,0,0,0,0, +3,3,3,0,2,2,3,2,0,0,1,0,0,2,3,1,0,0,0,0,0,0,2,0,2,0,0,0,2,0,0,0, +0,1,1,0,0,0,1,0,0,1,0,1,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,0,0,0,0,0,0,0,1,0,0,2,2,2,2,0,0,1,0,0,2,0,0,0,0,0,2,0,1,0, +0,0,2,1,1,0,1,0,2,1,1,0,0,1,1,2,1,0,2,0,2,0,1,0,0,0,2,0,0,0,0,0, +0,0,0,2,2,0,2,1,1,1,1,2,2,0,0,1,0,1,0,0,1,3,0,0,0,0,1,0,0,2,1,0, +0,0,1,0,1,0,0,0,0,0,2,1,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, +2,0,0,2,3,0,2,3,1,2,2,0,2,0,0,2,0,2,1,1,1,2,1,0,0,1,2,1,1,2,1,0, +1,0,2,0,1,0,1,1,0,0,2,2,1,2,1,1,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,2,1,2,0,0,0,1,0,0,3,2,0,1,0,0,1,0,0,2,0,0,0,1,2,1,0,1,0, +0,0,0,0,1,0,1,0,0,1,0,0,0,0,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,2,2,0,2,2,1,1,0,1,1,1,1,1,0,0,1,2,1,1,1,0,1,0,0,0,1,1,1,1, +0,0,2,1,0,1,1,1,0,1,1,2,1,2,1,1,2,0,1,1,2,1,0,2,0,0,0,0,0,0,0,0, +3,2,2,0,0,2,0,0,0,0,0,0,0,2,2,0,2,0,0,1,0,0,2,0,0,0,0,0,2,0,0,0, +0,2,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,3,2,0,2,2,0,1,1,0,1,0,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0, +2,0,1,0,1,0,1,1,0,0,1,2,0,1,0,1,1,0,0,1,0,1,0,2,0,0,0,0,0,0,0,0, +2,2,2,0,1,1,0,0,0,1,0,0,0,1,2,0,1,0,0,1,0,0,1,0,0,0,0,1,2,0,1,0, +0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,1,0,1,1,1,0,0,0,0,1,2,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +1,1,2,0,1,0,0,0,1,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,2,0,0,0,0,0,1, +0,0,1,2,2,0,2,1,2,1,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,0,0,0,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0, +2,2,2,0,0,0,1,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,0,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +) + +Latin5TurkishModel = { + 'char_to_order_map': Latin5_TurkishCharToOrderMap, + 'precedence_matrix': TurkishLangModel, + 'typical_positive_ratio': 0.970290, + 'keep_english_letter': True, + 'charset_name': "ISO-8859-9", + 'language': 'Turkish', +} diff --git a/robot/lib/python3.8/site-packages/chardet/latin1prober.py b/robot/lib/python3.8/site-packages/chardet/latin1prober.py new file mode 100644 index 0000000000000000000000000000000000000000..7d1e8c20fb09ddaa0254ae74cbd4425ffdc5dcdc --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/latin1prober.py @@ -0,0 +1,145 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .enums import ProbingState + +FREQ_CAT_NUM = 4 + +UDF = 0 # undefined +OTH = 1 # other +ASC = 2 # ascii capital letter +ASS = 3 # ascii small letter +ACV = 4 # accent capital vowel +ACO = 5 # accent capital other +ASV = 6 # accent small vowel +ASO = 7 # accent small other +CLASS_NUM = 8 # total classes + +Latin1_CharToClass = ( + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 00 - 07 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 08 - 0F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 10 - 17 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 18 - 1F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 20 - 27 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 28 - 2F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 30 - 37 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 38 - 3F + OTH, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 40 - 47 + ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 48 - 4F + ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 50 - 57 + ASC, ASC, ASC, OTH, OTH, OTH, OTH, OTH, # 58 - 5F + OTH, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 60 - 67 + ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 68 - 6F + ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 70 - 77 + ASS, ASS, ASS, OTH, OTH, OTH, OTH, OTH, # 78 - 7F + OTH, UDF, OTH, ASO, OTH, OTH, OTH, OTH, # 80 - 87 + OTH, OTH, ACO, OTH, ACO, UDF, ACO, UDF, # 88 - 8F + UDF, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 90 - 97 + OTH, OTH, ASO, OTH, ASO, UDF, ASO, ACO, # 98 - 9F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A0 - A7 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A8 - AF + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B0 - B7 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B8 - BF + ACV, ACV, ACV, ACV, ACV, ACV, ACO, ACO, # C0 - C7 + ACV, ACV, ACV, ACV, ACV, ACV, ACV, ACV, # C8 - CF + ACO, ACO, ACV, ACV, ACV, ACV, ACV, OTH, # D0 - D7 + ACV, ACV, ACV, ACV, ACV, ACO, ACO, ACO, # D8 - DF + ASV, ASV, ASV, ASV, ASV, ASV, ASO, ASO, # E0 - E7 + ASV, ASV, ASV, ASV, ASV, ASV, ASV, ASV, # E8 - EF + ASO, ASO, ASV, ASV, ASV, ASV, ASV, OTH, # F0 - F7 + ASV, ASV, ASV, ASV, ASV, ASO, ASO, ASO, # F8 - FF +) + +# 0 : illegal +# 1 : very unlikely +# 2 : normal +# 3 : very likely +Latin1ClassModel = ( +# UDF OTH ASC ASS ACV ACO ASV ASO + 0, 0, 0, 0, 0, 0, 0, 0, # UDF + 0, 3, 3, 3, 3, 3, 3, 3, # OTH + 0, 3, 3, 3, 3, 3, 3, 3, # ASC + 0, 3, 3, 3, 1, 1, 3, 3, # ASS + 0, 3, 3, 3, 1, 2, 1, 2, # ACV + 0, 3, 3, 3, 3, 3, 3, 3, # ACO + 0, 3, 1, 3, 1, 1, 1, 3, # ASV + 0, 3, 1, 3, 1, 1, 3, 3, # ASO +) + + +class Latin1Prober(CharSetProber): + def __init__(self): + super(Latin1Prober, self).__init__() + self._last_char_class = None + self._freq_counter = None + self.reset() + + def reset(self): + self._last_char_class = OTH + self._freq_counter = [0] * FREQ_CAT_NUM + CharSetProber.reset(self) + + @property + def charset_name(self): + return "ISO-8859-1" + + @property + def language(self): + return "" + + def feed(self, byte_str): + byte_str = self.filter_with_english_letters(byte_str) + for c in byte_str: + char_class = Latin1_CharToClass[c] + freq = Latin1ClassModel[(self._last_char_class * CLASS_NUM) + + char_class] + if freq == 0: + self._state = ProbingState.NOT_ME + break + self._freq_counter[freq] += 1 + self._last_char_class = char_class + + return self.state + + def get_confidence(self): + if self.state == ProbingState.NOT_ME: + return 0.01 + + total = sum(self._freq_counter) + if total < 0.01: + confidence = 0.0 + else: + confidence = ((self._freq_counter[3] - self._freq_counter[1] * 20.0) + / total) + if confidence < 0.0: + confidence = 0.0 + # lower the confidence of latin1 so that other more accurate + # detector can take priority. + confidence = confidence * 0.73 + return confidence diff --git a/robot/lib/python3.8/site-packages/chardet/mbcharsetprober.py b/robot/lib/python3.8/site-packages/chardet/mbcharsetprober.py new file mode 100644 index 0000000000000000000000000000000000000000..6256ecfd1e2c9ac4cfa3fac359cd12dce85b759c --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/mbcharsetprober.py @@ -0,0 +1,91 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Proofpoint, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .enums import ProbingState, MachineState + + +class MultiByteCharSetProber(CharSetProber): + """ + MultiByteCharSetProber + """ + + def __init__(self, lang_filter=None): + super(MultiByteCharSetProber, self).__init__(lang_filter=lang_filter) + self.distribution_analyzer = None + self.coding_sm = None + self._last_char = [0, 0] + + def reset(self): + super(MultiByteCharSetProber, self).reset() + if self.coding_sm: + self.coding_sm.reset() + if self.distribution_analyzer: + self.distribution_analyzer.reset() + self._last_char = [0, 0] + + @property + def charset_name(self): + raise NotImplementedError + + @property + def language(self): + raise NotImplementedError + + def feed(self, byte_str): + for i in range(len(byte_str)): + coding_state = self.coding_sm.next_state(byte_str[i]) + if coding_state == MachineState.ERROR: + self.logger.debug('%s %s prober hit error at byte %s', + self.charset_name, self.language, i) + self._state = ProbingState.NOT_ME + break + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + break + elif coding_state == MachineState.START: + char_len = self.coding_sm.get_current_charlen() + if i == 0: + self._last_char[1] = byte_str[0] + self.distribution_analyzer.feed(self._last_char, char_len) + else: + self.distribution_analyzer.feed(byte_str[i - 1:i + 1], + char_len) + + self._last_char[0] = byte_str[-1] + + if self.state == ProbingState.DETECTING: + if (self.distribution_analyzer.got_enough_data() and + (self.get_confidence() > self.SHORTCUT_THRESHOLD)): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + return self.distribution_analyzer.get_confidence() diff --git a/robot/lib/python3.8/site-packages/chardet/mbcsgroupprober.py b/robot/lib/python3.8/site-packages/chardet/mbcsgroupprober.py new file mode 100644 index 0000000000000000000000000000000000000000..530abe75e0c00cbfcb2a310d872866f320977d0a --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/mbcsgroupprober.py @@ -0,0 +1,54 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Proofpoint, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetgroupprober import CharSetGroupProber +from .utf8prober import UTF8Prober +from .sjisprober import SJISProber +from .eucjpprober import EUCJPProber +from .gb2312prober import GB2312Prober +from .euckrprober import EUCKRProber +from .cp949prober import CP949Prober +from .big5prober import Big5Prober +from .euctwprober import EUCTWProber + + +class MBCSGroupProber(CharSetGroupProber): + def __init__(self, lang_filter=None): + super(MBCSGroupProber, self).__init__(lang_filter=lang_filter) + self.probers = [ + UTF8Prober(), + SJISProber(), + EUCJPProber(), + GB2312Prober(), + EUCKRProber(), + CP949Prober(), + Big5Prober(), + EUCTWProber() + ] + self.reset() diff --git a/robot/lib/python3.8/site-packages/chardet/mbcssm.py b/robot/lib/python3.8/site-packages/chardet/mbcssm.py new file mode 100644 index 0000000000000000000000000000000000000000..8360d0f284ef394f2980b5bb89548e234385cdf1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/mbcssm.py @@ -0,0 +1,572 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .enums import MachineState + +# BIG5 + +BIG5_CLS = ( + 1,1,1,1,1,1,1,1, # 00 - 07 #allow 0x00 as legal value + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 1,1,1,1,1,1,1,1, # 30 - 37 + 1,1,1,1,1,1,1,1, # 38 - 3f + 2,2,2,2,2,2,2,2, # 40 - 47 + 2,2,2,2,2,2,2,2, # 48 - 4f + 2,2,2,2,2,2,2,2, # 50 - 57 + 2,2,2,2,2,2,2,2, # 58 - 5f + 2,2,2,2,2,2,2,2, # 60 - 67 + 2,2,2,2,2,2,2,2, # 68 - 6f + 2,2,2,2,2,2,2,2, # 70 - 77 + 2,2,2,2,2,2,2,1, # 78 - 7f + 4,4,4,4,4,4,4,4, # 80 - 87 + 4,4,4,4,4,4,4,4, # 88 - 8f + 4,4,4,4,4,4,4,4, # 90 - 97 + 4,4,4,4,4,4,4,4, # 98 - 9f + 4,3,3,3,3,3,3,3, # a0 - a7 + 3,3,3,3,3,3,3,3, # a8 - af + 3,3,3,3,3,3,3,3, # b0 - b7 + 3,3,3,3,3,3,3,3, # b8 - bf + 3,3,3,3,3,3,3,3, # c0 - c7 + 3,3,3,3,3,3,3,3, # c8 - cf + 3,3,3,3,3,3,3,3, # d0 - d7 + 3,3,3,3,3,3,3,3, # d8 - df + 3,3,3,3,3,3,3,3, # e0 - e7 + 3,3,3,3,3,3,3,3, # e8 - ef + 3,3,3,3,3,3,3,3, # f0 - f7 + 3,3,3,3,3,3,3,0 # f8 - ff +) + +BIG5_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,#08-0f + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START#10-17 +) + +BIG5_CHAR_LEN_TABLE = (0, 1, 1, 2, 0) + +BIG5_SM_MODEL = {'class_table': BIG5_CLS, + 'class_factor': 5, + 'state_table': BIG5_ST, + 'char_len_table': BIG5_CHAR_LEN_TABLE, + 'name': 'Big5'} + +# CP949 + +CP949_CLS = ( + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,0,0, # 00 - 0f + 1,1,1,1,1,1,1,1, 1,1,1,0,1,1,1,1, # 10 - 1f + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 20 - 2f + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 30 - 3f + 1,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, # 40 - 4f + 4,4,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 50 - 5f + 1,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5, # 60 - 6f + 5,5,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 70 - 7f + 0,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 80 - 8f + 6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 90 - 9f + 6,7,7,7,7,7,7,7, 7,7,7,7,7,8,8,8, # a0 - af + 7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7, # b0 - bf + 7,7,7,7,7,7,9,2, 2,3,2,2,2,2,2,2, # c0 - cf + 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # d0 - df + 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # e0 - ef + 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,0, # f0 - ff +) + +CP949_ST = ( +#cls= 0 1 2 3 4 5 6 7 8 9 # previous state = + MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START, 4, 5,MachineState.ERROR, 6, # MachineState.START + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, # MachineState.ERROR + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME, # MachineState.ITS_ME + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 3 + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 4 + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 5 + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 6 +) + +CP949_CHAR_LEN_TABLE = (0, 1, 2, 0, 1, 1, 2, 2, 0, 2) + +CP949_SM_MODEL = {'class_table': CP949_CLS, + 'class_factor': 10, + 'state_table': CP949_ST, + 'char_len_table': CP949_CHAR_LEN_TABLE, + 'name': 'CP949'} + +# EUC-JP + +EUCJP_CLS = ( + 4,4,4,4,4,4,4,4, # 00 - 07 + 4,4,4,4,4,4,5,5, # 08 - 0f + 4,4,4,4,4,4,4,4, # 10 - 17 + 4,4,4,5,4,4,4,4, # 18 - 1f + 4,4,4,4,4,4,4,4, # 20 - 27 + 4,4,4,4,4,4,4,4, # 28 - 2f + 4,4,4,4,4,4,4,4, # 30 - 37 + 4,4,4,4,4,4,4,4, # 38 - 3f + 4,4,4,4,4,4,4,4, # 40 - 47 + 4,4,4,4,4,4,4,4, # 48 - 4f + 4,4,4,4,4,4,4,4, # 50 - 57 + 4,4,4,4,4,4,4,4, # 58 - 5f + 4,4,4,4,4,4,4,4, # 60 - 67 + 4,4,4,4,4,4,4,4, # 68 - 6f + 4,4,4,4,4,4,4,4, # 70 - 77 + 4,4,4,4,4,4,4,4, # 78 - 7f + 5,5,5,5,5,5,5,5, # 80 - 87 + 5,5,5,5,5,5,1,3, # 88 - 8f + 5,5,5,5,5,5,5,5, # 90 - 97 + 5,5,5,5,5,5,5,5, # 98 - 9f + 5,2,2,2,2,2,2,2, # a0 - a7 + 2,2,2,2,2,2,2,2, # a8 - af + 2,2,2,2,2,2,2,2, # b0 - b7 + 2,2,2,2,2,2,2,2, # b8 - bf + 2,2,2,2,2,2,2,2, # c0 - c7 + 2,2,2,2,2,2,2,2, # c8 - cf + 2,2,2,2,2,2,2,2, # d0 - d7 + 2,2,2,2,2,2,2,2, # d8 - df + 0,0,0,0,0,0,0,0, # e0 - e7 + 0,0,0,0,0,0,0,0, # e8 - ef + 0,0,0,0,0,0,0,0, # f0 - f7 + 0,0,0,0,0,0,0,5 # f8 - ff +) + +EUCJP_ST = ( + 3, 4, 3, 5,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.START,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 3,MachineState.ERROR,#18-1f + 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START#20-27 +) + +EUCJP_CHAR_LEN_TABLE = (2, 2, 2, 3, 1, 0) + +EUCJP_SM_MODEL = {'class_table': EUCJP_CLS, + 'class_factor': 6, + 'state_table': EUCJP_ST, + 'char_len_table': EUCJP_CHAR_LEN_TABLE, + 'name': 'EUC-JP'} + +# EUC-KR + +EUCKR_CLS = ( + 1,1,1,1,1,1,1,1, # 00 - 07 + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 1,1,1,1,1,1,1,1, # 30 - 37 + 1,1,1,1,1,1,1,1, # 38 - 3f + 1,1,1,1,1,1,1,1, # 40 - 47 + 1,1,1,1,1,1,1,1, # 48 - 4f + 1,1,1,1,1,1,1,1, # 50 - 57 + 1,1,1,1,1,1,1,1, # 58 - 5f + 1,1,1,1,1,1,1,1, # 60 - 67 + 1,1,1,1,1,1,1,1, # 68 - 6f + 1,1,1,1,1,1,1,1, # 70 - 77 + 1,1,1,1,1,1,1,1, # 78 - 7f + 0,0,0,0,0,0,0,0, # 80 - 87 + 0,0,0,0,0,0,0,0, # 88 - 8f + 0,0,0,0,0,0,0,0, # 90 - 97 + 0,0,0,0,0,0,0,0, # 98 - 9f + 0,2,2,2,2,2,2,2, # a0 - a7 + 2,2,2,2,2,3,3,3, # a8 - af + 2,2,2,2,2,2,2,2, # b0 - b7 + 2,2,2,2,2,2,2,2, # b8 - bf + 2,2,2,2,2,2,2,2, # c0 - c7 + 2,3,2,2,2,2,2,2, # c8 - cf + 2,2,2,2,2,2,2,2, # d0 - d7 + 2,2,2,2,2,2,2,2, # d8 - df + 2,2,2,2,2,2,2,2, # e0 - e7 + 2,2,2,2,2,2,2,2, # e8 - ef + 2,2,2,2,2,2,2,2, # f0 - f7 + 2,2,2,2,2,2,2,0 # f8 - ff +) + +EUCKR_ST = ( + MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #08-0f +) + +EUCKR_CHAR_LEN_TABLE = (0, 1, 2, 0) + +EUCKR_SM_MODEL = {'class_table': EUCKR_CLS, + 'class_factor': 4, + 'state_table': EUCKR_ST, + 'char_len_table': EUCKR_CHAR_LEN_TABLE, + 'name': 'EUC-KR'} + +# EUC-TW + +EUCTW_CLS = ( + 2,2,2,2,2,2,2,2, # 00 - 07 + 2,2,2,2,2,2,0,0, # 08 - 0f + 2,2,2,2,2,2,2,2, # 10 - 17 + 2,2,2,0,2,2,2,2, # 18 - 1f + 2,2,2,2,2,2,2,2, # 20 - 27 + 2,2,2,2,2,2,2,2, # 28 - 2f + 2,2,2,2,2,2,2,2, # 30 - 37 + 2,2,2,2,2,2,2,2, # 38 - 3f + 2,2,2,2,2,2,2,2, # 40 - 47 + 2,2,2,2,2,2,2,2, # 48 - 4f + 2,2,2,2,2,2,2,2, # 50 - 57 + 2,2,2,2,2,2,2,2, # 58 - 5f + 2,2,2,2,2,2,2,2, # 60 - 67 + 2,2,2,2,2,2,2,2, # 68 - 6f + 2,2,2,2,2,2,2,2, # 70 - 77 + 2,2,2,2,2,2,2,2, # 78 - 7f + 0,0,0,0,0,0,0,0, # 80 - 87 + 0,0,0,0,0,0,6,0, # 88 - 8f + 0,0,0,0,0,0,0,0, # 90 - 97 + 0,0,0,0,0,0,0,0, # 98 - 9f + 0,3,4,4,4,4,4,4, # a0 - a7 + 5,5,1,1,1,1,1,1, # a8 - af + 1,1,1,1,1,1,1,1, # b0 - b7 + 1,1,1,1,1,1,1,1, # b8 - bf + 1,1,3,1,3,3,3,3, # c0 - c7 + 3,3,3,3,3,3,3,3, # c8 - cf + 3,3,3,3,3,3,3,3, # d0 - d7 + 3,3,3,3,3,3,3,3, # d8 - df + 3,3,3,3,3,3,3,3, # e0 - e7 + 3,3,3,3,3,3,3,3, # e8 - ef + 3,3,3,3,3,3,3,3, # f0 - f7 + 3,3,3,3,3,3,3,0 # f8 - ff +) + +EUCTW_ST = ( + MachineState.ERROR,MachineState.ERROR,MachineState.START, 3, 3, 3, 4,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,MachineState.ERROR,#10-17 + MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.START,MachineState.START,#20-27 + MachineState.START,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START #28-2f +) + +EUCTW_CHAR_LEN_TABLE = (0, 0, 1, 2, 2, 2, 3) + +EUCTW_SM_MODEL = {'class_table': EUCTW_CLS, + 'class_factor': 7, + 'state_table': EUCTW_ST, + 'char_len_table': EUCTW_CHAR_LEN_TABLE, + 'name': 'x-euc-tw'} + +# GB2312 + +GB2312_CLS = ( + 1,1,1,1,1,1,1,1, # 00 - 07 + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 3,3,3,3,3,3,3,3, # 30 - 37 + 3,3,1,1,1,1,1,1, # 38 - 3f + 2,2,2,2,2,2,2,2, # 40 - 47 + 2,2,2,2,2,2,2,2, # 48 - 4f + 2,2,2,2,2,2,2,2, # 50 - 57 + 2,2,2,2,2,2,2,2, # 58 - 5f + 2,2,2,2,2,2,2,2, # 60 - 67 + 2,2,2,2,2,2,2,2, # 68 - 6f + 2,2,2,2,2,2,2,2, # 70 - 77 + 2,2,2,2,2,2,2,4, # 78 - 7f + 5,6,6,6,6,6,6,6, # 80 - 87 + 6,6,6,6,6,6,6,6, # 88 - 8f + 6,6,6,6,6,6,6,6, # 90 - 97 + 6,6,6,6,6,6,6,6, # 98 - 9f + 6,6,6,6,6,6,6,6, # a0 - a7 + 6,6,6,6,6,6,6,6, # a8 - af + 6,6,6,6,6,6,6,6, # b0 - b7 + 6,6,6,6,6,6,6,6, # b8 - bf + 6,6,6,6,6,6,6,6, # c0 - c7 + 6,6,6,6,6,6,6,6, # c8 - cf + 6,6,6,6,6,6,6,6, # d0 - d7 + 6,6,6,6,6,6,6,6, # d8 - df + 6,6,6,6,6,6,6,6, # e0 - e7 + 6,6,6,6,6,6,6,6, # e8 - ef + 6,6,6,6,6,6,6,6, # f0 - f7 + 6,6,6,6,6,6,6,0 # f8 - ff +) + +GB2312_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, 3,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,#10-17 + 4,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + MachineState.ERROR,MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#20-27 + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START #28-2f +) + +# To be accurate, the length of class 6 can be either 2 or 4. +# But it is not necessary to discriminate between the two since +# it is used for frequency analysis only, and we are validating +# each code range there as well. So it is safe to set it to be +# 2 here. +GB2312_CHAR_LEN_TABLE = (0, 1, 1, 1, 1, 1, 2) + +GB2312_SM_MODEL = {'class_table': GB2312_CLS, + 'class_factor': 7, + 'state_table': GB2312_ST, + 'char_len_table': GB2312_CHAR_LEN_TABLE, + 'name': 'GB2312'} + +# Shift_JIS + +SJIS_CLS = ( + 1,1,1,1,1,1,1,1, # 00 - 07 + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 1,1,1,1,1,1,1,1, # 30 - 37 + 1,1,1,1,1,1,1,1, # 38 - 3f + 2,2,2,2,2,2,2,2, # 40 - 47 + 2,2,2,2,2,2,2,2, # 48 - 4f + 2,2,2,2,2,2,2,2, # 50 - 57 + 2,2,2,2,2,2,2,2, # 58 - 5f + 2,2,2,2,2,2,2,2, # 60 - 67 + 2,2,2,2,2,2,2,2, # 68 - 6f + 2,2,2,2,2,2,2,2, # 70 - 77 + 2,2,2,2,2,2,2,1, # 78 - 7f + 3,3,3,3,3,2,2,3, # 80 - 87 + 3,3,3,3,3,3,3,3, # 88 - 8f + 3,3,3,3,3,3,3,3, # 90 - 97 + 3,3,3,3,3,3,3,3, # 98 - 9f + #0xa0 is illegal in sjis encoding, but some pages does + #contain such byte. We need to be more error forgiven. + 2,2,2,2,2,2,2,2, # a0 - a7 + 2,2,2,2,2,2,2,2, # a8 - af + 2,2,2,2,2,2,2,2, # b0 - b7 + 2,2,2,2,2,2,2,2, # b8 - bf + 2,2,2,2,2,2,2,2, # c0 - c7 + 2,2,2,2,2,2,2,2, # c8 - cf + 2,2,2,2,2,2,2,2, # d0 - d7 + 2,2,2,2,2,2,2,2, # d8 - df + 3,3,3,3,3,3,3,3, # e0 - e7 + 3,3,3,3,3,4,4,4, # e8 - ef + 3,3,3,3,3,3,3,3, # f0 - f7 + 3,3,3,3,3,0,0,0) # f8 - ff + + +SJIS_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START #10-17 +) + +SJIS_CHAR_LEN_TABLE = (0, 1, 1, 2, 0, 0) + +SJIS_SM_MODEL = {'class_table': SJIS_CLS, + 'class_factor': 6, + 'state_table': SJIS_ST, + 'char_len_table': SJIS_CHAR_LEN_TABLE, + 'name': 'Shift_JIS'} + +# UCS2-BE + +UCS2BE_CLS = ( + 0,0,0,0,0,0,0,0, # 00 - 07 + 0,0,1,0,0,2,0,0, # 08 - 0f + 0,0,0,0,0,0,0,0, # 10 - 17 + 0,0,0,3,0,0,0,0, # 18 - 1f + 0,0,0,0,0,0,0,0, # 20 - 27 + 0,3,3,3,3,3,0,0, # 28 - 2f + 0,0,0,0,0,0,0,0, # 30 - 37 + 0,0,0,0,0,0,0,0, # 38 - 3f + 0,0,0,0,0,0,0,0, # 40 - 47 + 0,0,0,0,0,0,0,0, # 48 - 4f + 0,0,0,0,0,0,0,0, # 50 - 57 + 0,0,0,0,0,0,0,0, # 58 - 5f + 0,0,0,0,0,0,0,0, # 60 - 67 + 0,0,0,0,0,0,0,0, # 68 - 6f + 0,0,0,0,0,0,0,0, # 70 - 77 + 0,0,0,0,0,0,0,0, # 78 - 7f + 0,0,0,0,0,0,0,0, # 80 - 87 + 0,0,0,0,0,0,0,0, # 88 - 8f + 0,0,0,0,0,0,0,0, # 90 - 97 + 0,0,0,0,0,0,0,0, # 98 - 9f + 0,0,0,0,0,0,0,0, # a0 - a7 + 0,0,0,0,0,0,0,0, # a8 - af + 0,0,0,0,0,0,0,0, # b0 - b7 + 0,0,0,0,0,0,0,0, # b8 - bf + 0,0,0,0,0,0,0,0, # c0 - c7 + 0,0,0,0,0,0,0,0, # c8 - cf + 0,0,0,0,0,0,0,0, # d0 - d7 + 0,0,0,0,0,0,0,0, # d8 - df + 0,0,0,0,0,0,0,0, # e0 - e7 + 0,0,0,0,0,0,0,0, # e8 - ef + 0,0,0,0,0,0,0,0, # f0 - f7 + 0,0,0,0,0,0,4,5 # f8 - ff +) + +UCS2BE_ST = ( + 5, 7, 7,MachineState.ERROR, 4, 3,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME, 6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,#10-17 + 6, 6, 6, 6, 6,MachineState.ITS_ME, 6, 6,#18-1f + 6, 6, 6, 6, 5, 7, 7,MachineState.ERROR,#20-27 + 5, 8, 6, 6,MachineState.ERROR, 6, 6, 6,#28-2f + 6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #30-37 +) + +UCS2BE_CHAR_LEN_TABLE = (2, 2, 2, 0, 2, 2) + +UCS2BE_SM_MODEL = {'class_table': UCS2BE_CLS, + 'class_factor': 6, + 'state_table': UCS2BE_ST, + 'char_len_table': UCS2BE_CHAR_LEN_TABLE, + 'name': 'UTF-16BE'} + +# UCS2-LE + +UCS2LE_CLS = ( + 0,0,0,0,0,0,0,0, # 00 - 07 + 0,0,1,0,0,2,0,0, # 08 - 0f + 0,0,0,0,0,0,0,0, # 10 - 17 + 0,0,0,3,0,0,0,0, # 18 - 1f + 0,0,0,0,0,0,0,0, # 20 - 27 + 0,3,3,3,3,3,0,0, # 28 - 2f + 0,0,0,0,0,0,0,0, # 30 - 37 + 0,0,0,0,0,0,0,0, # 38 - 3f + 0,0,0,0,0,0,0,0, # 40 - 47 + 0,0,0,0,0,0,0,0, # 48 - 4f + 0,0,0,0,0,0,0,0, # 50 - 57 + 0,0,0,0,0,0,0,0, # 58 - 5f + 0,0,0,0,0,0,0,0, # 60 - 67 + 0,0,0,0,0,0,0,0, # 68 - 6f + 0,0,0,0,0,0,0,0, # 70 - 77 + 0,0,0,0,0,0,0,0, # 78 - 7f + 0,0,0,0,0,0,0,0, # 80 - 87 + 0,0,0,0,0,0,0,0, # 88 - 8f + 0,0,0,0,0,0,0,0, # 90 - 97 + 0,0,0,0,0,0,0,0, # 98 - 9f + 0,0,0,0,0,0,0,0, # a0 - a7 + 0,0,0,0,0,0,0,0, # a8 - af + 0,0,0,0,0,0,0,0, # b0 - b7 + 0,0,0,0,0,0,0,0, # b8 - bf + 0,0,0,0,0,0,0,0, # c0 - c7 + 0,0,0,0,0,0,0,0, # c8 - cf + 0,0,0,0,0,0,0,0, # d0 - d7 + 0,0,0,0,0,0,0,0, # d8 - df + 0,0,0,0,0,0,0,0, # e0 - e7 + 0,0,0,0,0,0,0,0, # e8 - ef + 0,0,0,0,0,0,0,0, # f0 - f7 + 0,0,0,0,0,0,4,5 # f8 - ff +) + +UCS2LE_ST = ( + 6, 6, 7, 6, 4, 3,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME, 5, 5, 5,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#10-17 + 5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR, 6, 6,#18-1f + 7, 6, 8, 8, 5, 5, 5,MachineState.ERROR,#20-27 + 5, 5, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5,#28-2f + 5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR,MachineState.START,MachineState.START #30-37 +) + +UCS2LE_CHAR_LEN_TABLE = (2, 2, 2, 2, 2, 2) + +UCS2LE_SM_MODEL = {'class_table': UCS2LE_CLS, + 'class_factor': 6, + 'state_table': UCS2LE_ST, + 'char_len_table': UCS2LE_CHAR_LEN_TABLE, + 'name': 'UTF-16LE'} + +# UTF-8 + +UTF8_CLS = ( + 1,1,1,1,1,1,1,1, # 00 - 07 #allow 0x00 as a legal value + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 1,1,1,1,1,1,1,1, # 30 - 37 + 1,1,1,1,1,1,1,1, # 38 - 3f + 1,1,1,1,1,1,1,1, # 40 - 47 + 1,1,1,1,1,1,1,1, # 48 - 4f + 1,1,1,1,1,1,1,1, # 50 - 57 + 1,1,1,1,1,1,1,1, # 58 - 5f + 1,1,1,1,1,1,1,1, # 60 - 67 + 1,1,1,1,1,1,1,1, # 68 - 6f + 1,1,1,1,1,1,1,1, # 70 - 77 + 1,1,1,1,1,1,1,1, # 78 - 7f + 2,2,2,2,3,3,3,3, # 80 - 87 + 4,4,4,4,4,4,4,4, # 88 - 8f + 4,4,4,4,4,4,4,4, # 90 - 97 + 4,4,4,4,4,4,4,4, # 98 - 9f + 5,5,5,5,5,5,5,5, # a0 - a7 + 5,5,5,5,5,5,5,5, # a8 - af + 5,5,5,5,5,5,5,5, # b0 - b7 + 5,5,5,5,5,5,5,5, # b8 - bf + 0,0,6,6,6,6,6,6, # c0 - c7 + 6,6,6,6,6,6,6,6, # c8 - cf + 6,6,6,6,6,6,6,6, # d0 - d7 + 6,6,6,6,6,6,6,6, # d8 - df + 7,8,8,8,8,8,8,8, # e0 - e7 + 8,8,8,8,8,9,8,8, # e8 - ef + 10,11,11,11,11,11,11,11, # f0 - f7 + 12,13,13,13,14,15,0,0 # f8 - ff +) + +UTF8_ST = ( + MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12, 10,#00-07 + 9, 11, 8, 7, 6, 5, 4, 3,#08-0f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#20-27 + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#28-2f + MachineState.ERROR,MachineState.ERROR, 5, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#30-37 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#38-3f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#40-47 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#48-4f + MachineState.ERROR,MachineState.ERROR, 7, 7, 7, 7,MachineState.ERROR,MachineState.ERROR,#50-57 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#58-5f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 7, 7,MachineState.ERROR,MachineState.ERROR,#60-67 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#68-6f + MachineState.ERROR,MachineState.ERROR, 9, 9, 9, 9,MachineState.ERROR,MachineState.ERROR,#70-77 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#78-7f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 9,MachineState.ERROR,MachineState.ERROR,#80-87 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#88-8f + MachineState.ERROR,MachineState.ERROR, 12, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,#90-97 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#98-9f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12,MachineState.ERROR,MachineState.ERROR,#a0-a7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#a8-af + MachineState.ERROR,MachineState.ERROR, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b0-b7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b8-bf + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,#c0-c7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR #c8-cf +) + +UTF8_CHAR_LEN_TABLE = (0, 1, 0, 0, 0, 0, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6) + +UTF8_SM_MODEL = {'class_table': UTF8_CLS, + 'class_factor': 16, + 'state_table': UTF8_ST, + 'char_len_table': UTF8_CHAR_LEN_TABLE, + 'name': 'UTF-8'} diff --git a/robot/lib/python3.8/site-packages/chardet/sbcharsetprober.py b/robot/lib/python3.8/site-packages/chardet/sbcharsetprober.py new file mode 100644 index 0000000000000000000000000000000000000000..0adb51de5a210aa36849cd149ed0f4ae424fce42 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/sbcharsetprober.py @@ -0,0 +1,132 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .enums import CharacterCategory, ProbingState, SequenceLikelihood + + +class SingleByteCharSetProber(CharSetProber): + SAMPLE_SIZE = 64 + SB_ENOUGH_REL_THRESHOLD = 1024 # 0.25 * SAMPLE_SIZE^2 + POSITIVE_SHORTCUT_THRESHOLD = 0.95 + NEGATIVE_SHORTCUT_THRESHOLD = 0.05 + + def __init__(self, model, reversed=False, name_prober=None): + super(SingleByteCharSetProber, self).__init__() + self._model = model + # TRUE if we need to reverse every pair in the model lookup + self._reversed = reversed + # Optional auxiliary prober for name decision + self._name_prober = name_prober + self._last_order = None + self._seq_counters = None + self._total_seqs = None + self._total_char = None + self._freq_char = None + self.reset() + + def reset(self): + super(SingleByteCharSetProber, self).reset() + # char order of last character + self._last_order = 255 + self._seq_counters = [0] * SequenceLikelihood.get_num_categories() + self._total_seqs = 0 + self._total_char = 0 + # characters that fall in our sampling range + self._freq_char = 0 + + @property + def charset_name(self): + if self._name_prober: + return self._name_prober.charset_name + else: + return self._model['charset_name'] + + @property + def language(self): + if self._name_prober: + return self._name_prober.language + else: + return self._model.get('language') + + def feed(self, byte_str): + if not self._model['keep_english_letter']: + byte_str = self.filter_international_words(byte_str) + if not byte_str: + return self.state + char_to_order_map = self._model['char_to_order_map'] + for i, c in enumerate(byte_str): + # XXX: Order is in range 1-64, so one would think we want 0-63 here, + # but that leads to 27 more test failures than before. + order = char_to_order_map[c] + # XXX: This was SYMBOL_CAT_ORDER before, with a value of 250, but + # CharacterCategory.SYMBOL is actually 253, so we use CONTROL + # to make it closer to the original intent. The only difference + # is whether or not we count digits and control characters for + # _total_char purposes. + if order < CharacterCategory.CONTROL: + self._total_char += 1 + if order < self.SAMPLE_SIZE: + self._freq_char += 1 + if self._last_order < self.SAMPLE_SIZE: + self._total_seqs += 1 + if not self._reversed: + i = (self._last_order * self.SAMPLE_SIZE) + order + model = self._model['precedence_matrix'][i] + else: # reverse the order of the letters in the lookup + i = (order * self.SAMPLE_SIZE) + self._last_order + model = self._model['precedence_matrix'][i] + self._seq_counters[model] += 1 + self._last_order = order + + charset_name = self._model['charset_name'] + if self.state == ProbingState.DETECTING: + if self._total_seqs > self.SB_ENOUGH_REL_THRESHOLD: + confidence = self.get_confidence() + if confidence > self.POSITIVE_SHORTCUT_THRESHOLD: + self.logger.debug('%s confidence = %s, we have a winner', + charset_name, confidence) + self._state = ProbingState.FOUND_IT + elif confidence < self.NEGATIVE_SHORTCUT_THRESHOLD: + self.logger.debug('%s confidence = %s, below negative ' + 'shortcut threshhold %s', charset_name, + confidence, + self.NEGATIVE_SHORTCUT_THRESHOLD) + self._state = ProbingState.NOT_ME + + return self.state + + def get_confidence(self): + r = 0.01 + if self._total_seqs > 0: + r = ((1.0 * self._seq_counters[SequenceLikelihood.POSITIVE]) / + self._total_seqs / self._model['typical_positive_ratio']) + r = r * self._freq_char / self._total_char + if r >= 1.0: + r = 0.99 + return r diff --git a/robot/lib/python3.8/site-packages/chardet/sbcsgroupprober.py b/robot/lib/python3.8/site-packages/chardet/sbcsgroupprober.py new file mode 100644 index 0000000000000000000000000000000000000000..98e95dc1a3cbc65e97bc726ab7000955132719dd --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/sbcsgroupprober.py @@ -0,0 +1,73 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetgroupprober import CharSetGroupProber +from .sbcharsetprober import SingleByteCharSetProber +from .langcyrillicmodel import (Win1251CyrillicModel, Koi8rModel, + Latin5CyrillicModel, MacCyrillicModel, + Ibm866Model, Ibm855Model) +from .langgreekmodel import Latin7GreekModel, Win1253GreekModel +from .langbulgarianmodel import Latin5BulgarianModel, Win1251BulgarianModel +# from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel +from .langthaimodel import TIS620ThaiModel +from .langhebrewmodel import Win1255HebrewModel +from .hebrewprober import HebrewProber +from .langturkishmodel import Latin5TurkishModel + + +class SBCSGroupProber(CharSetGroupProber): + def __init__(self): + super(SBCSGroupProber, self).__init__() + self.probers = [ + SingleByteCharSetProber(Win1251CyrillicModel), + SingleByteCharSetProber(Koi8rModel), + SingleByteCharSetProber(Latin5CyrillicModel), + SingleByteCharSetProber(MacCyrillicModel), + SingleByteCharSetProber(Ibm866Model), + SingleByteCharSetProber(Ibm855Model), + SingleByteCharSetProber(Latin7GreekModel), + SingleByteCharSetProber(Win1253GreekModel), + SingleByteCharSetProber(Latin5BulgarianModel), + SingleByteCharSetProber(Win1251BulgarianModel), + # TODO: Restore Hungarian encodings (iso-8859-2 and windows-1250) + # after we retrain model. + # SingleByteCharSetProber(Latin2HungarianModel), + # SingleByteCharSetProber(Win1250HungarianModel), + SingleByteCharSetProber(TIS620ThaiModel), + SingleByteCharSetProber(Latin5TurkishModel), + ] + hebrew_prober = HebrewProber() + logical_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, + False, hebrew_prober) + visual_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, True, + hebrew_prober) + hebrew_prober.set_model_probers(logical_hebrew_prober, visual_hebrew_prober) + self.probers.extend([hebrew_prober, logical_hebrew_prober, + visual_hebrew_prober]) + + self.reset() diff --git a/robot/lib/python3.8/site-packages/chardet/sjisprober.py b/robot/lib/python3.8/site-packages/chardet/sjisprober.py new file mode 100644 index 0000000000000000000000000000000000000000..9e29623bdc54a7c6d11bcc167d71bb44cc9be39d --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/sjisprober.py @@ -0,0 +1,92 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import SJISDistributionAnalysis +from .jpcntx import SJISContextAnalysis +from .mbcssm import SJIS_SM_MODEL +from .enums import ProbingState, MachineState + + +class SJISProber(MultiByteCharSetProber): + def __init__(self): + super(SJISProber, self).__init__() + self.coding_sm = CodingStateMachine(SJIS_SM_MODEL) + self.distribution_analyzer = SJISDistributionAnalysis() + self.context_analyzer = SJISContextAnalysis() + self.reset() + + def reset(self): + super(SJISProber, self).reset() + self.context_analyzer.reset() + + @property + def charset_name(self): + return self.context_analyzer.charset_name + + @property + def language(self): + return "Japanese" + + def feed(self, byte_str): + for i in range(len(byte_str)): + coding_state = self.coding_sm.next_state(byte_str[i]) + if coding_state == MachineState.ERROR: + self.logger.debug('%s %s prober hit error at byte %s', + self.charset_name, self.language, i) + self._state = ProbingState.NOT_ME + break + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + break + elif coding_state == MachineState.START: + char_len = self.coding_sm.get_current_charlen() + if i == 0: + self._last_char[1] = byte_str[0] + self.context_analyzer.feed(self._last_char[2 - char_len:], + char_len) + self.distribution_analyzer.feed(self._last_char, char_len) + else: + self.context_analyzer.feed(byte_str[i + 1 - char_len:i + 3 + - char_len], char_len) + self.distribution_analyzer.feed(byte_str[i - 1:i + 1], + char_len) + + self._last_char[0] = byte_str[-1] + + if self.state == ProbingState.DETECTING: + if (self.context_analyzer.got_enough_data() and + (self.get_confidence() > self.SHORTCUT_THRESHOLD)): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + context_conf = self.context_analyzer.get_confidence() + distrib_conf = self.distribution_analyzer.get_confidence() + return max(context_conf, distrib_conf) diff --git a/robot/lib/python3.8/site-packages/chardet/universaldetector.py b/robot/lib/python3.8/site-packages/chardet/universaldetector.py new file mode 100644 index 0000000000000000000000000000000000000000..7b4e92d6158527736e3d14d6f725edada8f94b00 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/universaldetector.py @@ -0,0 +1,286 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### +""" +Module containing the UniversalDetector detector class, which is the primary +class a user of ``chardet`` should use. + +:author: Mark Pilgrim (initial port to Python) +:author: Shy Shalom (original C code) +:author: Dan Blanchard (major refactoring for 3.0) +:author: Ian Cordasco +""" + + +import codecs +import logging +import re + +from .charsetgroupprober import CharSetGroupProber +from .enums import InputState, LanguageFilter, ProbingState +from .escprober import EscCharSetProber +from .latin1prober import Latin1Prober +from .mbcsgroupprober import MBCSGroupProber +from .sbcsgroupprober import SBCSGroupProber + + +class UniversalDetector(object): + """ + The ``UniversalDetector`` class underlies the ``chardet.detect`` function + and coordinates all of the different charset probers. + + To get a ``dict`` containing an encoding and its confidence, you can simply + run: + + .. code:: + + u = UniversalDetector() + u.feed(some_bytes) + u.close() + detected = u.result + + """ + + MINIMUM_THRESHOLD = 0.20 + HIGH_BYTE_DETECTOR = re.compile(b'[\x80-\xFF]') + ESC_DETECTOR = re.compile(b'(\033|~{)') + WIN_BYTE_DETECTOR = re.compile(b'[\x80-\x9F]') + ISO_WIN_MAP = {'iso-8859-1': 'Windows-1252', + 'iso-8859-2': 'Windows-1250', + 'iso-8859-5': 'Windows-1251', + 'iso-8859-6': 'Windows-1256', + 'iso-8859-7': 'Windows-1253', + 'iso-8859-8': 'Windows-1255', + 'iso-8859-9': 'Windows-1254', + 'iso-8859-13': 'Windows-1257'} + + def __init__(self, lang_filter=LanguageFilter.ALL): + self._esc_charset_prober = None + self._charset_probers = [] + self.result = None + self.done = None + self._got_data = None + self._input_state = None + self._last_char = None + self.lang_filter = lang_filter + self.logger = logging.getLogger(__name__) + self._has_win_bytes = None + self.reset() + + def reset(self): + """ + Reset the UniversalDetector and all of its probers back to their + initial states. This is called by ``__init__``, so you only need to + call this directly in between analyses of different documents. + """ + self.result = {'encoding': None, 'confidence': 0.0, 'language': None} + self.done = False + self._got_data = False + self._has_win_bytes = False + self._input_state = InputState.PURE_ASCII + self._last_char = b'' + if self._esc_charset_prober: + self._esc_charset_prober.reset() + for prober in self._charset_probers: + prober.reset() + + def feed(self, byte_str): + """ + Takes a chunk of a document and feeds it through all of the relevant + charset probers. + + After calling ``feed``, you can check the value of the ``done`` + attribute to see if you need to continue feeding the + ``UniversalDetector`` more data, or if it has made a prediction + (in the ``result`` attribute). + + .. note:: + You should always call ``close`` when you're done feeding in your + document if ``done`` is not already ``True``. + """ + if self.done: + return + + if not len(byte_str): + return + + if not isinstance(byte_str, bytearray): + byte_str = bytearray(byte_str) + + # First check for known BOMs, since these are guaranteed to be correct + if not self._got_data: + # If the data starts with BOM, we know it is UTF + if byte_str.startswith(codecs.BOM_UTF8): + # EF BB BF UTF-8 with BOM + self.result = {'encoding': "UTF-8-SIG", + 'confidence': 1.0, + 'language': ''} + elif byte_str.startswith((codecs.BOM_UTF32_LE, + codecs.BOM_UTF32_BE)): + # FF FE 00 00 UTF-32, little-endian BOM + # 00 00 FE FF UTF-32, big-endian BOM + self.result = {'encoding': "UTF-32", + 'confidence': 1.0, + 'language': ''} + elif byte_str.startswith(b'\xFE\xFF\x00\x00'): + # FE FF 00 00 UCS-4, unusual octet order BOM (3412) + self.result = {'encoding': "X-ISO-10646-UCS-4-3412", + 'confidence': 1.0, + 'language': ''} + elif byte_str.startswith(b'\x00\x00\xFF\xFE'): + # 00 00 FF FE UCS-4, unusual octet order BOM (2143) + self.result = {'encoding': "X-ISO-10646-UCS-4-2143", + 'confidence': 1.0, + 'language': ''} + elif byte_str.startswith((codecs.BOM_LE, codecs.BOM_BE)): + # FF FE UTF-16, little endian BOM + # FE FF UTF-16, big endian BOM + self.result = {'encoding': "UTF-16", + 'confidence': 1.0, + 'language': ''} + + self._got_data = True + if self.result['encoding'] is not None: + self.done = True + return + + # If none of those matched and we've only see ASCII so far, check + # for high bytes and escape sequences + if self._input_state == InputState.PURE_ASCII: + if self.HIGH_BYTE_DETECTOR.search(byte_str): + self._input_state = InputState.HIGH_BYTE + elif self._input_state == InputState.PURE_ASCII and \ + self.ESC_DETECTOR.search(self._last_char + byte_str): + self._input_state = InputState.ESC_ASCII + + self._last_char = byte_str[-1:] + + # If we've seen escape sequences, use the EscCharSetProber, which + # uses a simple state machine to check for known escape sequences in + # HZ and ISO-2022 encodings, since those are the only encodings that + # use such sequences. + if self._input_state == InputState.ESC_ASCII: + if not self._esc_charset_prober: + self._esc_charset_prober = EscCharSetProber(self.lang_filter) + if self._esc_charset_prober.feed(byte_str) == ProbingState.FOUND_IT: + self.result = {'encoding': + self._esc_charset_prober.charset_name, + 'confidence': + self._esc_charset_prober.get_confidence(), + 'language': + self._esc_charset_prober.language} + self.done = True + # If we've seen high bytes (i.e., those with values greater than 127), + # we need to do more complicated checks using all our multi-byte and + # single-byte probers that are left. The single-byte probers + # use character bigram distributions to determine the encoding, whereas + # the multi-byte probers use a combination of character unigram and + # bigram distributions. + elif self._input_state == InputState.HIGH_BYTE: + if not self._charset_probers: + self._charset_probers = [MBCSGroupProber(self.lang_filter)] + # If we're checking non-CJK encodings, use single-byte prober + if self.lang_filter & LanguageFilter.NON_CJK: + self._charset_probers.append(SBCSGroupProber()) + self._charset_probers.append(Latin1Prober()) + for prober in self._charset_probers: + if prober.feed(byte_str) == ProbingState.FOUND_IT: + self.result = {'encoding': prober.charset_name, + 'confidence': prober.get_confidence(), + 'language': prober.language} + self.done = True + break + if self.WIN_BYTE_DETECTOR.search(byte_str): + self._has_win_bytes = True + + def close(self): + """ + Stop analyzing the current document and come up with a final + prediction. + + :returns: The ``result`` attribute, a ``dict`` with the keys + `encoding`, `confidence`, and `language`. + """ + # Don't bother with checks if we're already done + if self.done: + return self.result + self.done = True + + if not self._got_data: + self.logger.debug('no data received!') + + # Default to ASCII if it is all we've seen so far + elif self._input_state == InputState.PURE_ASCII: + self.result = {'encoding': 'ascii', + 'confidence': 1.0, + 'language': ''} + + # If we have seen non-ASCII, return the best that met MINIMUM_THRESHOLD + elif self._input_state == InputState.HIGH_BYTE: + prober_confidence = None + max_prober_confidence = 0.0 + max_prober = None + for prober in self._charset_probers: + if not prober: + continue + prober_confidence = prober.get_confidence() + if prober_confidence > max_prober_confidence: + max_prober_confidence = prober_confidence + max_prober = prober + if max_prober and (max_prober_confidence > self.MINIMUM_THRESHOLD): + charset_name = max_prober.charset_name + lower_charset_name = max_prober.charset_name.lower() + confidence = max_prober.get_confidence() + # Use Windows encoding name instead of ISO-8859 if we saw any + # extra Windows-specific bytes + if lower_charset_name.startswith('iso-8859'): + if self._has_win_bytes: + charset_name = self.ISO_WIN_MAP.get(lower_charset_name, + charset_name) + self.result = {'encoding': charset_name, + 'confidence': confidence, + 'language': max_prober.language} + + # Log all prober confidences if none met MINIMUM_THRESHOLD + if self.logger.getEffectiveLevel() == logging.DEBUG: + if self.result['encoding'] is None: + self.logger.debug('no probers hit minimum threshold') + for group_prober in self._charset_probers: + if not group_prober: + continue + if isinstance(group_prober, CharSetGroupProber): + for prober in group_prober.probers: + self.logger.debug('%s %s confidence = %s', + prober.charset_name, + prober.language, + prober.get_confidence()) + else: + self.logger.debug('%s %s confidence = %s', + prober.charset_name, + prober.language, + prober.get_confidence()) + return self.result diff --git a/robot/lib/python3.8/site-packages/chardet/utf8prober.py b/robot/lib/python3.8/site-packages/chardet/utf8prober.py new file mode 100644 index 0000000000000000000000000000000000000000..6c3196cc2d7e46e6756580267f5643c6f7b448dd --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/utf8prober.py @@ -0,0 +1,82 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .enums import ProbingState, MachineState +from .codingstatemachine import CodingStateMachine +from .mbcssm import UTF8_SM_MODEL + + + +class UTF8Prober(CharSetProber): + ONE_CHAR_PROB = 0.5 + + def __init__(self): + super(UTF8Prober, self).__init__() + self.coding_sm = CodingStateMachine(UTF8_SM_MODEL) + self._num_mb_chars = None + self.reset() + + def reset(self): + super(UTF8Prober, self).reset() + self.coding_sm.reset() + self._num_mb_chars = 0 + + @property + def charset_name(self): + return "utf-8" + + @property + def language(self): + return "" + + def feed(self, byte_str): + for c in byte_str: + coding_state = self.coding_sm.next_state(c) + if coding_state == MachineState.ERROR: + self._state = ProbingState.NOT_ME + break + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + break + elif coding_state == MachineState.START: + if self.coding_sm.get_current_charlen() >= 2: + self._num_mb_chars += 1 + + if self.state == ProbingState.DETECTING: + if self.get_confidence() > self.SHORTCUT_THRESHOLD: + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + unlike = 0.99 + if self._num_mb_chars < 6: + unlike *= self.ONE_CHAR_PROB ** self._num_mb_chars + return 1.0 - unlike + else: + return unlike diff --git a/robot/lib/python3.8/site-packages/chardet/version.py b/robot/lib/python3.8/site-packages/chardet/version.py new file mode 100644 index 0000000000000000000000000000000000000000..bb2a34a70ea760ad1f58979acfc8fa466e30c511 --- /dev/null +++ b/robot/lib/python3.8/site-packages/chardet/version.py @@ -0,0 +1,9 @@ +""" +This module exists only to simplify retrieving the version number of chardet +from within setup.py and from chardet subpackages. + +:author: Dan Blanchard (dan.blanchard@gmail.com) +""" + +__version__ = "3.0.4" +VERSION = __version__.split('.') diff --git a/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/LICENSE.rst b/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/LICENSE.rst new file mode 100644 index 0000000000000000000000000000000000000000..d12a849186982399c537c5b9a8fd77bf2edd5eab --- /dev/null +++ b/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/LICENSE.rst @@ -0,0 +1,28 @@ +Copyright 2014 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/METADATA b/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..00d697493a2fa9ff774f5db7571477b2d616ae6d --- /dev/null +++ b/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/METADATA @@ -0,0 +1,102 @@ +Metadata-Version: 2.1 +Name: click +Version: 7.1.2 +Summary: Composable command line interface toolkit +Home-page: https://palletsprojects.com/p/click/ +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Documentation, https://click.palletsprojects.com/ +Project-URL: Code, https://github.com/pallets/click +Project-URL: Issue tracker, https://github.com/pallets/click/issues +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* + +\$ click\_ +========== + +Click is a Python package for creating beautiful command line interfaces +in a composable way with as little code as necessary. It's the "Command +Line Interface Creation Kit". It's highly configurable but comes with +sensible defaults out of the box. + +It aims to make the process of writing command line tools quick and fun +while also preventing any frustration caused by the inability to +implement an intended CLI API. + +Click in three points: + +- Arbitrary nesting of commands +- Automatic help page generation +- Supports lazy loading of subcommands at runtime + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U click + +.. _pip: https://pip.pypa.io/en/stable/quickstart/ + + +A Simple Example +---------------- + +.. code-block:: python + + import click + + @click.command() + @click.option("--count", default=1, help="Number of greetings.") + @click.option("--name", prompt="Your name", help="The person to greet.") + def hello(count, name): + """Simple program that greets NAME for a total of COUNT times.""" + for _ in range(count): + click.echo(f"Hello, {name}!") + + if __name__ == '__main__': + hello() + +.. code-block:: text + + $ python hello.py --count=3 + Your name: Click + Hello, Click! + Hello, Click! + Hello, Click! + + +Donate +------ + +The Pallets organization develops and supports Click and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Website: https://palletsprojects.com/p/click/ +- Documentation: https://click.palletsprojects.com/ +- Releases: https://pypi.org/project/click/ +- Code: https://github.com/pallets/click +- Issue tracker: https://github.com/pallets/click/issues +- Test status: https://dev.azure.com/pallets/click/_build +- Official chat: https://discord.gg/t6rrQZH + + diff --git a/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/RECORD b/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..8fc797da18967ed083f03fa7b346b0150753fa6e --- /dev/null +++ b/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/RECORD @@ -0,0 +1,40 @@ +click-7.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +click-7.1.2.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475 +click-7.1.2.dist-info/METADATA,sha256=LrRgakZKV7Yg3qJqX_plu2WhFW81MzP3EqQmZhHIO8M,2868 +click-7.1.2.dist-info/RECORD,, +click-7.1.2.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +click-7.1.2.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6 +click/__init__.py,sha256=FkyGDQ-cbiQxP_lxgUspyFYS48f2S_pTcfKPz-d_RMo,2463 +click/__pycache__/__init__.cpython-38.pyc,, +click/__pycache__/_bashcomplete.cpython-38.pyc,, +click/__pycache__/_compat.cpython-38.pyc,, +click/__pycache__/_termui_impl.cpython-38.pyc,, +click/__pycache__/_textwrap.cpython-38.pyc,, +click/__pycache__/_unicodefun.cpython-38.pyc,, +click/__pycache__/_winconsole.cpython-38.pyc,, +click/__pycache__/core.cpython-38.pyc,, +click/__pycache__/decorators.cpython-38.pyc,, +click/__pycache__/exceptions.cpython-38.pyc,, +click/__pycache__/formatting.cpython-38.pyc,, +click/__pycache__/globals.cpython-38.pyc,, +click/__pycache__/parser.cpython-38.pyc,, +click/__pycache__/termui.cpython-38.pyc,, +click/__pycache__/testing.cpython-38.pyc,, +click/__pycache__/types.cpython-38.pyc,, +click/__pycache__/utils.cpython-38.pyc,, +click/_bashcomplete.py,sha256=9J98IHQYmCAr2Jup6TDshUr5FJEen-AoQCZR0K5nKxQ,12309 +click/_compat.py,sha256=AoMaYnZ-3pwtNXuHtlb6_UXsayoG0QZiHKIRy2VFezc,24169 +click/_termui_impl.py,sha256=yNktUMAdjYOU1HMkq915jR3zgAzUNtGSQqSTSSMn3eQ,20702 +click/_textwrap.py,sha256=ajCzkzFly5tjm9foQ5N9_MOeaYJMBjAltuFa69n4iXY,1197 +click/_unicodefun.py,sha256=apLSNEBZgUsQNPMUv072zJ1swqnm0dYVT5TqcIWTt6w,4201 +click/_winconsole.py,sha256=6YDu6Rq1Wxx4w9uinBMK2LHvP83aerZM9GQurlk3QDo,10010 +click/core.py,sha256=V6DJzastGhrC6WTDwV9MSLwcJUdX2Uf1ypmgkjBdn_Y,77650 +click/decorators.py,sha256=3TvEO_BkaHl7k6Eh1G5eC7JK4LKPdpFqH9JP0QDyTlM,11215 +click/exceptions.py,sha256=3pQAyyMFzx5A3eV0Y27WtDTyGogZRbrC6_o5DjjKBbw,8118 +click/formatting.py,sha256=Wb4gqFEpWaKPgAbOvnkCl8p-bEZx5KpM5ZSByhlnJNk,9281 +click/globals.py,sha256=ht7u2kUGI08pAarB4e4yC8Lkkxy6gJfRZyzxEj8EbWQ,1501 +click/parser.py,sha256=mFK-k58JtPpqO0AC36WAr0t5UfzEw1mvgVSyn7WCe9M,15691 +click/termui.py,sha256=G7QBEKIepRIGLvNdGwBTYiEtSImRxvTO_AglVpyHH2s,23998 +click/testing.py,sha256=EUEsDUqNXFgCLhZ0ZFOROpaVDA5I_rijwnNPE6qICgA,12854 +click/types.py,sha256=wuubik4VqgqAw5dvbYFkDt-zSAx97y9TQXuXcVaRyQA,25045 +click/utils.py,sha256=4VEcJ7iEHwjnFuzEuRtkT99o5VG3zqSD7Q2CVzv13WU,15940 diff --git a/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/WHEEL b/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ef99c6cf3283b50a273ac4c6d009a0aa85597070 --- /dev/null +++ b/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..dca9a909647e3b066931de2909c2d1e65c78c995 --- /dev/null +++ b/robot/lib/python3.8/site-packages/click-7.1.2.dist-info/top_level.txt @@ -0,0 +1 @@ +click diff --git a/robot/lib/python3.8/site-packages/click/__init__.py b/robot/lib/python3.8/site-packages/click/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2b6008f2dd4176d819f06e0d7e92e43b142d30b0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/__init__.py @@ -0,0 +1,79 @@ +""" +Click is a simple Python module inspired by the stdlib optparse to make +writing command line scripts fun. Unlike other modules, it's based +around a simple API that does not come with too much magic and is +composable. +""" +from .core import Argument +from .core import BaseCommand +from .core import Command +from .core import CommandCollection +from .core import Context +from .core import Group +from .core import MultiCommand +from .core import Option +from .core import Parameter +from .decorators import argument +from .decorators import command +from .decorators import confirmation_option +from .decorators import group +from .decorators import help_option +from .decorators import make_pass_decorator +from .decorators import option +from .decorators import pass_context +from .decorators import pass_obj +from .decorators import password_option +from .decorators import version_option +from .exceptions import Abort +from .exceptions import BadArgumentUsage +from .exceptions import BadOptionUsage +from .exceptions import BadParameter +from .exceptions import ClickException +from .exceptions import FileError +from .exceptions import MissingParameter +from .exceptions import NoSuchOption +from .exceptions import UsageError +from .formatting import HelpFormatter +from .formatting import wrap_text +from .globals import get_current_context +from .parser import OptionParser +from .termui import clear +from .termui import confirm +from .termui import echo_via_pager +from .termui import edit +from .termui import get_terminal_size +from .termui import getchar +from .termui import launch +from .termui import pause +from .termui import progressbar +from .termui import prompt +from .termui import secho +from .termui import style +from .termui import unstyle +from .types import BOOL +from .types import Choice +from .types import DateTime +from .types import File +from .types import FLOAT +from .types import FloatRange +from .types import INT +from .types import IntRange +from .types import ParamType +from .types import Path +from .types import STRING +from .types import Tuple +from .types import UNPROCESSED +from .types import UUID +from .utils import echo +from .utils import format_filename +from .utils import get_app_dir +from .utils import get_binary_stream +from .utils import get_os_args +from .utils import get_text_stream +from .utils import open_file + +# Controls if click should emit the warning about the use of unicode +# literals. +disable_unicode_literals_warning = False + +__version__ = "7.1.2" diff --git a/robot/lib/python3.8/site-packages/click/_bashcomplete.py b/robot/lib/python3.8/site-packages/click/_bashcomplete.py new file mode 100644 index 0000000000000000000000000000000000000000..8bca24480f751e0471a625bc36c05a61d49add87 --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/_bashcomplete.py @@ -0,0 +1,375 @@ +import copy +import os +import re + +from .core import Argument +from .core import MultiCommand +from .core import Option +from .parser import split_arg_string +from .types import Choice +from .utils import echo + +try: + from collections import abc +except ImportError: + import collections as abc + +WORDBREAK = "=" + +# Note, only BASH version 4.4 and later have the nosort option. +COMPLETION_SCRIPT_BASH = """ +%(complete_func)s() { + local IFS=$'\n' + COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\ + COMP_CWORD=$COMP_CWORD \\ + %(autocomplete_var)s=complete $1 ) ) + return 0 +} + +%(complete_func)setup() { + local COMPLETION_OPTIONS="" + local BASH_VERSION_ARR=(${BASH_VERSION//./ }) + # Only BASH version 4.4 and later have the nosort option. + if [ ${BASH_VERSION_ARR[0]} -gt 4 ] || ([ ${BASH_VERSION_ARR[0]} -eq 4 ] \ +&& [ ${BASH_VERSION_ARR[1]} -ge 4 ]); then + COMPLETION_OPTIONS="-o nosort" + fi + + complete $COMPLETION_OPTIONS -F %(complete_func)s %(script_names)s +} + +%(complete_func)setup +""" + +COMPLETION_SCRIPT_ZSH = """ +#compdef %(script_names)s + +%(complete_func)s() { + local -a completions + local -a completions_with_descriptions + local -a response + (( ! $+commands[%(script_names)s] )) && return 1 + + response=("${(@f)$( env COMP_WORDS=\"${words[*]}\" \\ + COMP_CWORD=$((CURRENT-1)) \\ + %(autocomplete_var)s=\"complete_zsh\" \\ + %(script_names)s )}") + + for key descr in ${(kv)response}; do + if [[ "$descr" == "_" ]]; then + completions+=("$key") + else + completions_with_descriptions+=("$key":"$descr") + fi + done + + if [ -n "$completions_with_descriptions" ]; then + _describe -V unsorted completions_with_descriptions -U + fi + + if [ -n "$completions" ]; then + compadd -U -V unsorted -a completions + fi + compstate[insert]="automenu" +} + +compdef %(complete_func)s %(script_names)s +""" + +COMPLETION_SCRIPT_FISH = ( + "complete --no-files --command %(script_names)s --arguments" + ' "(env %(autocomplete_var)s=complete_fish' + " COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t)" + ' %(script_names)s)"' +) + +_completion_scripts = { + "bash": COMPLETION_SCRIPT_BASH, + "zsh": COMPLETION_SCRIPT_ZSH, + "fish": COMPLETION_SCRIPT_FISH, +} + +_invalid_ident_char_re = re.compile(r"[^a-zA-Z0-9_]") + + +def get_completion_script(prog_name, complete_var, shell): + cf_name = _invalid_ident_char_re.sub("", prog_name.replace("-", "_")) + script = _completion_scripts.get(shell, COMPLETION_SCRIPT_BASH) + return ( + script + % { + "complete_func": "_{}_completion".format(cf_name), + "script_names": prog_name, + "autocomplete_var": complete_var, + } + ).strip() + ";" + + +def resolve_ctx(cli, prog_name, args): + """Parse into a hierarchy of contexts. Contexts are connected + through the parent variable. + + :param cli: command definition + :param prog_name: the program that is running + :param args: full list of args + :return: the final context/command parsed + """ + ctx = cli.make_context(prog_name, args, resilient_parsing=True) + args = ctx.protected_args + ctx.args + while args: + if isinstance(ctx.command, MultiCommand): + if not ctx.command.chain: + cmd_name, cmd, args = ctx.command.resolve_command(ctx, args) + if cmd is None: + return ctx + ctx = cmd.make_context( + cmd_name, args, parent=ctx, resilient_parsing=True + ) + args = ctx.protected_args + ctx.args + else: + # Walk chained subcommand contexts saving the last one. + while args: + cmd_name, cmd, args = ctx.command.resolve_command(ctx, args) + if cmd is None: + return ctx + sub_ctx = cmd.make_context( + cmd_name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + resilient_parsing=True, + ) + args = sub_ctx.args + ctx = sub_ctx + args = sub_ctx.protected_args + sub_ctx.args + else: + break + return ctx + + +def start_of_option(param_str): + """ + :param param_str: param_str to check + :return: whether or not this is the start of an option declaration + (i.e. starts "-" or "--") + """ + return param_str and param_str[:1] == "-" + + +def is_incomplete_option(all_args, cmd_param): + """ + :param all_args: the full original list of args supplied + :param cmd_param: the current command paramter + :return: whether or not the last option declaration (i.e. starts + "-" or "--") is incomplete and corresponds to this cmd_param. In + other words whether this cmd_param option can still accept + values + """ + if not isinstance(cmd_param, Option): + return False + if cmd_param.is_flag: + return False + last_option = None + for index, arg_str in enumerate( + reversed([arg for arg in all_args if arg != WORDBREAK]) + ): + if index + 1 > cmd_param.nargs: + break + if start_of_option(arg_str): + last_option = arg_str + + return True if last_option and last_option in cmd_param.opts else False + + +def is_incomplete_argument(current_params, cmd_param): + """ + :param current_params: the current params and values for this + argument as already entered + :param cmd_param: the current command parameter + :return: whether or not the last argument is incomplete and + corresponds to this cmd_param. In other words whether or not the + this cmd_param argument can still accept values + """ + if not isinstance(cmd_param, Argument): + return False + current_param_values = current_params[cmd_param.name] + if current_param_values is None: + return True + if cmd_param.nargs == -1: + return True + if ( + isinstance(current_param_values, abc.Iterable) + and cmd_param.nargs > 1 + and len(current_param_values) < cmd_param.nargs + ): + return True + return False + + +def get_user_autocompletions(ctx, args, incomplete, cmd_param): + """ + :param ctx: context associated with the parsed command + :param args: full list of args + :param incomplete: the incomplete text to autocomplete + :param cmd_param: command definition + :return: all the possible user-specified completions for the param + """ + results = [] + if isinstance(cmd_param.type, Choice): + # Choices don't support descriptions. + results = [ + (c, None) for c in cmd_param.type.choices if str(c).startswith(incomplete) + ] + elif cmd_param.autocompletion is not None: + dynamic_completions = cmd_param.autocompletion( + ctx=ctx, args=args, incomplete=incomplete + ) + results = [ + c if isinstance(c, tuple) else (c, None) for c in dynamic_completions + ] + return results + + +def get_visible_commands_starting_with(ctx, starts_with): + """ + :param ctx: context associated with the parsed command + :starts_with: string that visible commands must start with. + :return: all visible (not hidden) commands that start with starts_with. + """ + for c in ctx.command.list_commands(ctx): + if c.startswith(starts_with): + command = ctx.command.get_command(ctx, c) + if not command.hidden: + yield command + + +def add_subcommand_completions(ctx, incomplete, completions_out): + # Add subcommand completions. + if isinstance(ctx.command, MultiCommand): + completions_out.extend( + [ + (c.name, c.get_short_help_str()) + for c in get_visible_commands_starting_with(ctx, incomplete) + ] + ) + + # Walk up the context list and add any other completion + # possibilities from chained commands + while ctx.parent is not None: + ctx = ctx.parent + if isinstance(ctx.command, MultiCommand) and ctx.command.chain: + remaining_commands = [ + c + for c in get_visible_commands_starting_with(ctx, incomplete) + if c.name not in ctx.protected_args + ] + completions_out.extend( + [(c.name, c.get_short_help_str()) for c in remaining_commands] + ) + + +def get_choices(cli, prog_name, args, incomplete): + """ + :param cli: command definition + :param prog_name: the program that is running + :param args: full list of args + :param incomplete: the incomplete text to autocomplete + :return: all the possible completions for the incomplete + """ + all_args = copy.deepcopy(args) + + ctx = resolve_ctx(cli, prog_name, args) + if ctx is None: + return [] + + has_double_dash = "--" in all_args + + # In newer versions of bash long opts with '='s are partitioned, but + # it's easier to parse without the '=' + if start_of_option(incomplete) and WORDBREAK in incomplete: + partition_incomplete = incomplete.partition(WORDBREAK) + all_args.append(partition_incomplete[0]) + incomplete = partition_incomplete[2] + elif incomplete == WORDBREAK: + incomplete = "" + + completions = [] + if not has_double_dash and start_of_option(incomplete): + # completions for partial options + for param in ctx.command.params: + if isinstance(param, Option) and not param.hidden: + param_opts = [ + param_opt + for param_opt in param.opts + param.secondary_opts + if param_opt not in all_args or param.multiple + ] + completions.extend( + [(o, param.help) for o in param_opts if o.startswith(incomplete)] + ) + return completions + # completion for option values from user supplied values + for param in ctx.command.params: + if is_incomplete_option(all_args, param): + return get_user_autocompletions(ctx, all_args, incomplete, param) + # completion for argument values from user supplied values + for param in ctx.command.params: + if is_incomplete_argument(ctx.params, param): + return get_user_autocompletions(ctx, all_args, incomplete, param) + + add_subcommand_completions(ctx, incomplete, completions) + # Sort before returning so that proper ordering can be enforced in custom types. + return sorted(completions) + + +def do_complete(cli, prog_name, include_descriptions): + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + for item in get_choices(cli, prog_name, args, incomplete): + echo(item[0]) + if include_descriptions: + # ZSH has trouble dealing with empty array parameters when + # returned from commands, use '_' to indicate no description + # is present. + echo(item[1] if item[1] else "_") + + return True + + +def do_complete_fish(cli, prog_name): + cwords = split_arg_string(os.environ["COMP_WORDS"]) + incomplete = os.environ["COMP_CWORD"] + args = cwords[1:] + + for item in get_choices(cli, prog_name, args, incomplete): + if item[1]: + echo("{arg}\t{desc}".format(arg=item[0], desc=item[1])) + else: + echo(item[0]) + + return True + + +def bashcomplete(cli, prog_name, complete_var, complete_instr): + if "_" in complete_instr: + command, shell = complete_instr.split("_", 1) + else: + command = complete_instr + shell = "bash" + + if command == "source": + echo(get_completion_script(prog_name, complete_var, shell)) + return True + elif command == "complete": + if shell == "fish": + return do_complete_fish(cli, prog_name) + elif shell in {"bash", "zsh"}: + return do_complete(cli, prog_name, shell == "zsh") + + return False diff --git a/robot/lib/python3.8/site-packages/click/_compat.py b/robot/lib/python3.8/site-packages/click/_compat.py new file mode 100644 index 0000000000000000000000000000000000000000..60cb115bc5091c408f68d2461115d2e35bd47799 --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/_compat.py @@ -0,0 +1,786 @@ +# flake8: noqa +import codecs +import io +import os +import re +import sys +from weakref import WeakKeyDictionary + +PY2 = sys.version_info[0] == 2 +CYGWIN = sys.platform.startswith("cygwin") +MSYS2 = sys.platform.startswith("win") and ("GCC" in sys.version) +# Determine local App Engine environment, per Google's own suggestion +APP_ENGINE = "APPENGINE_RUNTIME" in os.environ and "Development/" in os.environ.get( + "SERVER_SOFTWARE", "" +) +WIN = sys.platform.startswith("win") and not APP_ENGINE and not MSYS2 +DEFAULT_COLUMNS = 80 + + +_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") + + +def get_filesystem_encoding(): + return sys.getfilesystemencoding() or sys.getdefaultencoding() + + +def _make_text_stream( + stream, encoding, errors, force_readable=False, force_writable=False +): + if encoding is None: + encoding = get_best_encoding(stream) + if errors is None: + errors = "replace" + return _NonClosingTextIOWrapper( + stream, + encoding, + errors, + line_buffering=True, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def is_ascii_encoding(encoding): + """Checks if a given encoding is ascii.""" + try: + return codecs.lookup(encoding).name == "ascii" + except LookupError: + return False + + +def get_best_encoding(stream): + """Returns the default stream encoding if not found.""" + rv = getattr(stream, "encoding", None) or sys.getdefaultencoding() + if is_ascii_encoding(rv): + return "utf-8" + return rv + + +class _NonClosingTextIOWrapper(io.TextIOWrapper): + def __init__( + self, + stream, + encoding, + errors, + force_readable=False, + force_writable=False, + **extra + ): + self._stream = stream = _FixupStream(stream, force_readable, force_writable) + io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra) + + # The io module is a place where the Python 3 text behavior + # was forced upon Python 2, so we need to unbreak + # it to look like Python 2. + if PY2: + + def write(self, x): + if isinstance(x, str) or is_bytes(x): + try: + self.flush() + except Exception: + pass + return self.buffer.write(str(x)) + return io.TextIOWrapper.write(self, x) + + def writelines(self, lines): + for line in lines: + self.write(line) + + def __del__(self): + try: + self.detach() + except Exception: + pass + + def isatty(self): + # https://bitbucket.org/pypy/pypy/issue/1803 + return self._stream.isatty() + + +class _FixupStream(object): + """The new io interface needs more from streams than streams + traditionally implement. As such, this fix-up code is necessary in + some circumstances. + + The forcing of readable and writable flags are there because some tools + put badly patched objects on sys (one such offender are certain version + of jupyter notebook). + """ + + def __init__(self, stream, force_readable=False, force_writable=False): + self._stream = stream + self._force_readable = force_readable + self._force_writable = force_writable + + def __getattr__(self, name): + return getattr(self._stream, name) + + def read1(self, size): + f = getattr(self._stream, "read1", None) + if f is not None: + return f(size) + # We only dispatch to readline instead of read in Python 2 as we + # do not want cause problems with the different implementation + # of line buffering. + if PY2: + return self._stream.readline(size) + return self._stream.read(size) + + def readable(self): + if self._force_readable: + return True + x = getattr(self._stream, "readable", None) + if x is not None: + return x() + try: + self._stream.read(0) + except Exception: + return False + return True + + def writable(self): + if self._force_writable: + return True + x = getattr(self._stream, "writable", None) + if x is not None: + return x() + try: + self._stream.write("") + except Exception: + try: + self._stream.write(b"") + except Exception: + return False + return True + + def seekable(self): + x = getattr(self._stream, "seekable", None) + if x is not None: + return x() + try: + self._stream.seek(self._stream.tell()) + except Exception: + return False + return True + + +if PY2: + text_type = unicode + raw_input = raw_input + string_types = (str, unicode) + int_types = (int, long) + iteritems = lambda x: x.iteritems() + range_type = xrange + + def is_bytes(x): + return isinstance(x, (buffer, bytearray)) + + _identifier_re = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$") + + # For Windows, we need to force stdout/stdin/stderr to binary if it's + # fetched for that. This obviously is not the most correct way to do + # it as it changes global state. Unfortunately, there does not seem to + # be a clear better way to do it as just reopening the file in binary + # mode does not change anything. + # + # An option would be to do what Python 3 does and to open the file as + # binary only, patch it back to the system, and then use a wrapper + # stream that converts newlines. It's not quite clear what's the + # correct option here. + # + # This code also lives in _winconsole for the fallback to the console + # emulation stream. + # + # There are also Windows environments where the `msvcrt` module is not + # available (which is why we use try-catch instead of the WIN variable + # here), such as the Google App Engine development server on Windows. In + # those cases there is just nothing we can do. + def set_binary_mode(f): + return f + + try: + import msvcrt + except ImportError: + pass + else: + + def set_binary_mode(f): + try: + fileno = f.fileno() + except Exception: + pass + else: + msvcrt.setmode(fileno, os.O_BINARY) + return f + + try: + import fcntl + except ImportError: + pass + else: + + def set_binary_mode(f): + try: + fileno = f.fileno() + except Exception: + pass + else: + flags = fcntl.fcntl(fileno, fcntl.F_GETFL) + fcntl.fcntl(fileno, fcntl.F_SETFL, flags & ~os.O_NONBLOCK) + return f + + def isidentifier(x): + return _identifier_re.search(x) is not None + + def get_binary_stdin(): + return set_binary_mode(sys.stdin) + + def get_binary_stdout(): + _wrap_std_stream("stdout") + return set_binary_mode(sys.stdout) + + def get_binary_stderr(): + _wrap_std_stream("stderr") + return set_binary_mode(sys.stderr) + + def get_text_stdin(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _make_text_stream(sys.stdin, encoding, errors, force_readable=True) + + def get_text_stdout(encoding=None, errors=None): + _wrap_std_stream("stdout") + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _make_text_stream(sys.stdout, encoding, errors, force_writable=True) + + def get_text_stderr(encoding=None, errors=None): + _wrap_std_stream("stderr") + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _make_text_stream(sys.stderr, encoding, errors, force_writable=True) + + def filename_to_ui(value): + if isinstance(value, bytes): + value = value.decode(get_filesystem_encoding(), "replace") + return value + + +else: + import io + + text_type = str + raw_input = input + string_types = (str,) + int_types = (int,) + range_type = range + isidentifier = lambda x: x.isidentifier() + iteritems = lambda x: iter(x.items()) + + def is_bytes(x): + return isinstance(x, (bytes, memoryview, bytearray)) + + def _is_binary_reader(stream, default=False): + try: + return isinstance(stream.read(0), bytes) + except Exception: + return default + # This happens in some cases where the stream was already + # closed. In this case, we assume the default. + + def _is_binary_writer(stream, default=False): + try: + stream.write(b"") + except Exception: + try: + stream.write("") + return False + except Exception: + pass + return default + return True + + def _find_binary_reader(stream): + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_reader(stream, False): + return stream + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_reader(buf, True): + return buf + + def _find_binary_writer(stream): + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detatching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_writer(stream, False): + return stream + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_writer(buf, True): + return buf + + def _stream_is_misconfigured(stream): + """A stream is misconfigured if its encoding is ASCII.""" + # If the stream does not have an encoding set, we assume it's set + # to ASCII. This appears to happen in certain unittest + # environments. It's not quite clear what the correct behavior is + # but this at least will force Click to recover somehow. + return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii") + + def _is_compat_stream_attr(stream, attr, value): + """A stream attribute is compatible if it is equal to the + desired value or the desired value is unset and the attribute + has a value. + """ + stream_value = getattr(stream, attr, None) + return stream_value == value or (value is None and stream_value is not None) + + def _is_compatible_text_stream(stream, encoding, errors): + """Check if a stream's encoding and errors attributes are + compatible with the desired values. + """ + return _is_compat_stream_attr( + stream, "encoding", encoding + ) and _is_compat_stream_attr(stream, "errors", errors) + + def _force_correct_text_stream( + text_stream, + encoding, + errors, + is_binary, + find_binary, + force_readable=False, + force_writable=False, + ): + if is_binary(text_stream, False): + binary_reader = text_stream + else: + # If the stream looks compatible, and won't default to a + # misconfigured ascii encoding, return it as-is. + if _is_compatible_text_stream(text_stream, encoding, errors) and not ( + encoding is None and _stream_is_misconfigured(text_stream) + ): + return text_stream + + # Otherwise, get the underlying binary reader. + binary_reader = find_binary(text_stream) + + # If that's not possible, silently use the original reader + # and get mojibake instead of exceptions. + if binary_reader is None: + return text_stream + + # Default errors to replace instead of strict in order to get + # something that works. + if errors is None: + errors = "replace" + + # Wrap the binary stream in a text stream with the correct + # encoding parameters. + return _make_text_stream( + binary_reader, + encoding, + errors, + force_readable=force_readable, + force_writable=force_writable, + ) + + def _force_correct_text_reader(text_reader, encoding, errors, force_readable=False): + return _force_correct_text_stream( + text_reader, + encoding, + errors, + _is_binary_reader, + _find_binary_reader, + force_readable=force_readable, + ) + + def _force_correct_text_writer(text_writer, encoding, errors, force_writable=False): + return _force_correct_text_stream( + text_writer, + encoding, + errors, + _is_binary_writer, + _find_binary_writer, + force_writable=force_writable, + ) + + def get_binary_stdin(): + reader = _find_binary_reader(sys.stdin) + if reader is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdin.") + return reader + + def get_binary_stdout(): + writer = _find_binary_writer(sys.stdout) + if writer is None: + raise RuntimeError( + "Was not able to determine binary stream for sys.stdout." + ) + return writer + + def get_binary_stderr(): + writer = _find_binary_writer(sys.stderr) + if writer is None: + raise RuntimeError( + "Was not able to determine binary stream for sys.stderr." + ) + return writer + + def get_text_stdin(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_reader( + sys.stdin, encoding, errors, force_readable=True + ) + + def get_text_stdout(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer( + sys.stdout, encoding, errors, force_writable=True + ) + + def get_text_stderr(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer( + sys.stderr, encoding, errors, force_writable=True + ) + + def filename_to_ui(value): + if isinstance(value, bytes): + value = value.decode(get_filesystem_encoding(), "replace") + else: + value = value.encode("utf-8", "surrogateescape").decode("utf-8", "replace") + return value + + +def get_streerror(e, default=None): + if hasattr(e, "strerror"): + msg = e.strerror + else: + if default is not None: + msg = default + else: + msg = str(e) + if isinstance(msg, bytes): + msg = msg.decode("utf-8", "replace") + return msg + + +def _wrap_io_open(file, mode, encoding, errors): + """On Python 2, :func:`io.open` returns a text file wrapper that + requires passing ``unicode`` to ``write``. Need to open the file in + binary mode then wrap it in a subclass that can write ``str`` and + ``unicode``. + + Also handles not passing ``encoding`` and ``errors`` in binary mode. + """ + binary = "b" in mode + + if binary: + kwargs = {} + else: + kwargs = {"encoding": encoding, "errors": errors} + + if not PY2 or binary: + return io.open(file, mode, **kwargs) + + f = io.open(file, "{}b".format(mode.replace("t", ""))) + return _make_text_stream(f, **kwargs) + + +def open_stream(filename, mode="r", encoding=None, errors="strict", atomic=False): + binary = "b" in mode + + # Standard streams first. These are simple because they don't need + # special handling for the atomic flag. It's entirely ignored. + if filename == "-": + if any(m in mode for m in ["w", "a", "x"]): + if binary: + return get_binary_stdout(), False + return get_text_stdout(encoding=encoding, errors=errors), False + if binary: + return get_binary_stdin(), False + return get_text_stdin(encoding=encoding, errors=errors), False + + # Non-atomic writes directly go out through the regular open functions. + if not atomic: + return _wrap_io_open(filename, mode, encoding, errors), True + + # Some usability stuff for atomic writes + if "a" in mode: + raise ValueError( + "Appending to an existing file is not supported, because that" + " would involve an expensive `copy`-operation to a temporary" + " file. Open the file in normal `w`-mode and copy explicitly" + " if that's what you're after." + ) + if "x" in mode: + raise ValueError("Use the `overwrite`-parameter instead.") + if "w" not in mode: + raise ValueError("Atomic writes only make sense with `w`-mode.") + + # Atomic writes are more complicated. They work by opening a file + # as a proxy in the same folder and then using the fdopen + # functionality to wrap it in a Python file. Then we wrap it in an + # atomic file that moves the file over on close. + import errno + import random + + try: + perm = os.stat(filename).st_mode + except OSError: + perm = None + + flags = os.O_RDWR | os.O_CREAT | os.O_EXCL + + if binary: + flags |= getattr(os, "O_BINARY", 0) + + while True: + tmp_filename = os.path.join( + os.path.dirname(filename), + ".__atomic-write{:08x}".format(random.randrange(1 << 32)), + ) + try: + fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm) + break + except OSError as e: + if e.errno == errno.EEXIST or ( + os.name == "nt" + and e.errno == errno.EACCES + and os.path.isdir(e.filename) + and os.access(e.filename, os.W_OK) + ): + continue + raise + + if perm is not None: + os.chmod(tmp_filename, perm) # in case perm includes bits in umask + + f = _wrap_io_open(fd, mode, encoding, errors) + return _AtomicFile(f, tmp_filename, os.path.realpath(filename)), True + + +# Used in a destructor call, needs extra protection from interpreter cleanup. +if hasattr(os, "replace"): + _replace = os.replace + _can_replace = True +else: + _replace = os.rename + _can_replace = not WIN + + +class _AtomicFile(object): + def __init__(self, f, tmp_filename, real_filename): + self._f = f + self._tmp_filename = tmp_filename + self._real_filename = real_filename + self.closed = False + + @property + def name(self): + return self._real_filename + + def close(self, delete=False): + if self.closed: + return + self._f.close() + if not _can_replace: + try: + os.remove(self._real_filename) + except OSError: + pass + _replace(self._tmp_filename, self._real_filename) + self.closed = True + + def __getattr__(self, name): + return getattr(self._f, name) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + self.close(delete=exc_type is not None) + + def __repr__(self): + return repr(self._f) + + +auto_wrap_for_ansi = None +colorama = None +get_winterm_size = None + + +def strip_ansi(value): + return _ansi_re.sub("", value) + + +def _is_jupyter_kernel_output(stream): + if WIN: + # TODO: Couldn't test on Windows, should't try to support until + # someone tests the details wrt colorama. + return + + while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)): + stream = stream._stream + + return stream.__class__.__module__.startswith("ipykernel.") + + +def should_strip_ansi(stream=None, color=None): + if color is None: + if stream is None: + stream = sys.stdin + return not isatty(stream) and not _is_jupyter_kernel_output(stream) + return not color + + +# If we're on Windows, we provide transparent integration through +# colorama. This will make ANSI colors through the echo function +# work automatically. +if WIN: + # Windows has a smaller terminal + DEFAULT_COLUMNS = 79 + + from ._winconsole import _get_windows_console_stream, _wrap_std_stream + + def _get_argv_encoding(): + import locale + + return locale.getpreferredencoding() + + if PY2: + + def raw_input(prompt=""): + sys.stderr.flush() + if prompt: + stdout = _default_text_stdout() + stdout.write(prompt) + stdin = _default_text_stdin() + return stdin.readline().rstrip("\r\n") + + try: + import colorama + except ImportError: + pass + else: + _ansi_stream_wrappers = WeakKeyDictionary() + + def auto_wrap_for_ansi(stream, color=None): + """This function wraps a stream so that calls through colorama + are issued to the win32 console API to recolor on demand. It + also ensures to reset the colors if a write call is interrupted + to not destroy the console afterwards. + """ + try: + cached = _ansi_stream_wrappers.get(stream) + except Exception: + cached = None + if cached is not None: + return cached + strip = should_strip_ansi(stream, color) + ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip) + rv = ansi_wrapper.stream + _write = rv.write + + def _safe_write(s): + try: + return _write(s) + except: + ansi_wrapper.reset_all() + raise + + rv.write = _safe_write + try: + _ansi_stream_wrappers[stream] = rv + except Exception: + pass + return rv + + def get_winterm_size(): + win = colorama.win32.GetConsoleScreenBufferInfo( + colorama.win32.STDOUT + ).srWindow + return win.Right - win.Left, win.Bottom - win.Top + + +else: + + def _get_argv_encoding(): + return getattr(sys.stdin, "encoding", None) or get_filesystem_encoding() + + _get_windows_console_stream = lambda *x: None + _wrap_std_stream = lambda *x: None + + +def term_len(x): + return len(strip_ansi(x)) + + +def isatty(stream): + try: + return stream.isatty() + except Exception: + return False + + +def _make_cached_stream_func(src_func, wrapper_func): + cache = WeakKeyDictionary() + + def func(): + stream = src_func() + try: + rv = cache.get(stream) + except Exception: + rv = None + if rv is not None: + return rv + rv = wrapper_func() + try: + stream = src_func() # In case wrapper_func() modified the stream + cache[stream] = rv + except Exception: + pass + return rv + + return func + + +_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin) +_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout) +_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr) + + +binary_streams = { + "stdin": get_binary_stdin, + "stdout": get_binary_stdout, + "stderr": get_binary_stderr, +} + +text_streams = { + "stdin": get_text_stdin, + "stdout": get_text_stdout, + "stderr": get_text_stderr, +} diff --git a/robot/lib/python3.8/site-packages/click/_termui_impl.py b/robot/lib/python3.8/site-packages/click/_termui_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..88bec37701cf549a7b39c7667a6520930c141a73 --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/_termui_impl.py @@ -0,0 +1,657 @@ +# -*- coding: utf-8 -*- +""" +This module contains implementations for the termui module. To keep the +import time of Click down, some infrequently used functionality is +placed in this module and only imported as needed. +""" +import contextlib +import math +import os +import sys +import time + +from ._compat import _default_text_stdout +from ._compat import CYGWIN +from ._compat import get_best_encoding +from ._compat import int_types +from ._compat import isatty +from ._compat import open_stream +from ._compat import range_type +from ._compat import strip_ansi +from ._compat import term_len +from ._compat import WIN +from .exceptions import ClickException +from .utils import echo + +if os.name == "nt": + BEFORE_BAR = "\r" + AFTER_BAR = "\n" +else: + BEFORE_BAR = "\r\033[?25l" + AFTER_BAR = "\033[?25h\n" + + +def _length_hint(obj): + """Returns the length hint of an object.""" + try: + return len(obj) + except (AttributeError, TypeError): + try: + get_hint = type(obj).__length_hint__ + except AttributeError: + return None + try: + hint = get_hint(obj) + except TypeError: + return None + if hint is NotImplemented or not isinstance(hint, int_types) or hint < 0: + return None + return hint + + +class ProgressBar(object): + def __init__( + self, + iterable, + length=None, + fill_char="#", + empty_char=" ", + bar_template="%(bar)s", + info_sep=" ", + show_eta=True, + show_percent=None, + show_pos=False, + item_show_func=None, + label=None, + file=None, + color=None, + width=30, + ): + self.fill_char = fill_char + self.empty_char = empty_char + self.bar_template = bar_template + self.info_sep = info_sep + self.show_eta = show_eta + self.show_percent = show_percent + self.show_pos = show_pos + self.item_show_func = item_show_func + self.label = label or "" + if file is None: + file = _default_text_stdout() + self.file = file + self.color = color + self.width = width + self.autowidth = width == 0 + + if length is None: + length = _length_hint(iterable) + if iterable is None: + if length is None: + raise TypeError("iterable or length is required") + iterable = range_type(length) + self.iter = iter(iterable) + self.length = length + self.length_known = length is not None + self.pos = 0 + self.avg = [] + self.start = self.last_eta = time.time() + self.eta_known = False + self.finished = False + self.max_width = None + self.entered = False + self.current_item = None + self.is_hidden = not isatty(self.file) + self._last_line = None + self.short_limit = 0.5 + + def __enter__(self): + self.entered = True + self.render_progress() + return self + + def __exit__(self, exc_type, exc_value, tb): + self.render_finish() + + def __iter__(self): + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + self.render_progress() + return self.generator() + + def __next__(self): + # Iteration is defined in terms of a generator function, + # returned by iter(self); use that to define next(). This works + # because `self.iter` is an iterable consumed by that generator, + # so it is re-entry safe. Calling `next(self.generator())` + # twice works and does "what you want". + return next(iter(self)) + + # Python 2 compat + next = __next__ + + def is_fast(self): + return time.time() - self.start <= self.short_limit + + def render_finish(self): + if self.is_hidden or self.is_fast(): + return + self.file.write(AFTER_BAR) + self.file.flush() + + @property + def pct(self): + if self.finished: + return 1.0 + return min(self.pos / (float(self.length) or 1), 1.0) + + @property + def time_per_iteration(self): + if not self.avg: + return 0.0 + return sum(self.avg) / float(len(self.avg)) + + @property + def eta(self): + if self.length_known and not self.finished: + return self.time_per_iteration * (self.length - self.pos) + return 0.0 + + def format_eta(self): + if self.eta_known: + t = int(self.eta) + seconds = t % 60 + t //= 60 + minutes = t % 60 + t //= 60 + hours = t % 24 + t //= 24 + if t > 0: + return "{}d {:02}:{:02}:{:02}".format(t, hours, minutes, seconds) + else: + return "{:02}:{:02}:{:02}".format(hours, minutes, seconds) + return "" + + def format_pos(self): + pos = str(self.pos) + if self.length_known: + pos += "/{}".format(self.length) + return pos + + def format_pct(self): + return "{: 4}%".format(int(self.pct * 100))[1:] + + def format_bar(self): + if self.length_known: + bar_length = int(self.pct * self.width) + bar = self.fill_char * bar_length + bar += self.empty_char * (self.width - bar_length) + elif self.finished: + bar = self.fill_char * self.width + else: + bar = list(self.empty_char * (self.width or 1)) + if self.time_per_iteration != 0: + bar[ + int( + (math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5) + * self.width + ) + ] = self.fill_char + bar = "".join(bar) + return bar + + def format_progress_line(self): + show_percent = self.show_percent + + info_bits = [] + if self.length_known and show_percent is None: + show_percent = not self.show_pos + + if self.show_pos: + info_bits.append(self.format_pos()) + if show_percent: + info_bits.append(self.format_pct()) + if self.show_eta and self.eta_known and not self.finished: + info_bits.append(self.format_eta()) + if self.item_show_func is not None: + item_info = self.item_show_func(self.current_item) + if item_info is not None: + info_bits.append(item_info) + + return ( + self.bar_template + % { + "label": self.label, + "bar": self.format_bar(), + "info": self.info_sep.join(info_bits), + } + ).rstrip() + + def render_progress(self): + from .termui import get_terminal_size + + if self.is_hidden: + return + + buf = [] + # Update width in case the terminal has been resized + if self.autowidth: + old_width = self.width + self.width = 0 + clutter_length = term_len(self.format_progress_line()) + new_width = max(0, get_terminal_size()[0] - clutter_length) + if new_width < old_width: + buf.append(BEFORE_BAR) + buf.append(" " * self.max_width) + self.max_width = new_width + self.width = new_width + + clear_width = self.width + if self.max_width is not None: + clear_width = self.max_width + + buf.append(BEFORE_BAR) + line = self.format_progress_line() + line_len = term_len(line) + if self.max_width is None or self.max_width < line_len: + self.max_width = line_len + + buf.append(line) + buf.append(" " * (clear_width - line_len)) + line = "".join(buf) + # Render the line only if it changed. + + if line != self._last_line and not self.is_fast(): + self._last_line = line + echo(line, file=self.file, color=self.color, nl=False) + self.file.flush() + + def make_step(self, n_steps): + self.pos += n_steps + if self.length_known and self.pos >= self.length: + self.finished = True + + if (time.time() - self.last_eta) < 1.0: + return + + self.last_eta = time.time() + + # self.avg is a rolling list of length <= 7 of steps where steps are + # defined as time elapsed divided by the total progress through + # self.length. + if self.pos: + step = (time.time() - self.start) / self.pos + else: + step = time.time() - self.start + + self.avg = self.avg[-6:] + [step] + + self.eta_known = self.length_known + + def update(self, n_steps): + self.make_step(n_steps) + self.render_progress() + + def finish(self): + self.eta_known = 0 + self.current_item = None + self.finished = True + + def generator(self): + """Return a generator which yields the items added to the bar + during construction, and updates the progress bar *after* the + yielded block returns. + """ + # WARNING: the iterator interface for `ProgressBar` relies on + # this and only works because this is a simple generator which + # doesn't create or manage additional state. If this function + # changes, the impact should be evaluated both against + # `iter(bar)` and `next(bar)`. `next()` in particular may call + # `self.generator()` repeatedly, and this must remain safe in + # order for that interface to work. + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + + if self.is_hidden: + for rv in self.iter: + yield rv + else: + for rv in self.iter: + self.current_item = rv + yield rv + self.update(1) + self.finish() + self.render_progress() + + +def pager(generator, color=None): + """Decide what method to use for paging through text.""" + stdout = _default_text_stdout() + if not isatty(sys.stdin) or not isatty(stdout): + return _nullpager(stdout, generator, color) + pager_cmd = (os.environ.get("PAGER", None) or "").strip() + if pager_cmd: + if WIN: + return _tempfilepager(generator, pager_cmd, color) + return _pipepager(generator, pager_cmd, color) + if os.environ.get("TERM") in ("dumb", "emacs"): + return _nullpager(stdout, generator, color) + if WIN or sys.platform.startswith("os2"): + return _tempfilepager(generator, "more <", color) + if hasattr(os, "system") and os.system("(less) 2>/dev/null") == 0: + return _pipepager(generator, "less", color) + + import tempfile + + fd, filename = tempfile.mkstemp() + os.close(fd) + try: + if hasattr(os, "system") and os.system('more "{}"'.format(filename)) == 0: + return _pipepager(generator, "more", color) + return _nullpager(stdout, generator, color) + finally: + os.unlink(filename) + + +def _pipepager(generator, cmd, color): + """Page through text by feeding it to another program. Invoking a + pager through this might support colors. + """ + import subprocess + + env = dict(os.environ) + + # If we're piping to less we might support colors under the + # condition that + cmd_detail = cmd.rsplit("/", 1)[-1].split() + if color is None and cmd_detail[0] == "less": + less_flags = "{}{}".format(os.environ.get("LESS", ""), " ".join(cmd_detail[1:])) + if not less_flags: + env["LESS"] = "-R" + color = True + elif "r" in less_flags or "R" in less_flags: + color = True + + c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env) + encoding = get_best_encoding(c.stdin) + try: + for text in generator: + if not color: + text = strip_ansi(text) + + c.stdin.write(text.encode(encoding, "replace")) + except (IOError, KeyboardInterrupt): + pass + else: + c.stdin.close() + + # Less doesn't respect ^C, but catches it for its own UI purposes (aborting + # search or other commands inside less). + # + # That means when the user hits ^C, the parent process (click) terminates, + # but less is still alive, paging the output and messing up the terminal. + # + # If the user wants to make the pager exit on ^C, they should set + # `LESS='-K'`. It's not our decision to make. + while True: + try: + c.wait() + except KeyboardInterrupt: + pass + else: + break + + +def _tempfilepager(generator, cmd, color): + """Page through text by invoking a program on a temporary file.""" + import tempfile + + filename = tempfile.mktemp() + # TODO: This never terminates if the passed generator never terminates. + text = "".join(generator) + if not color: + text = strip_ansi(text) + encoding = get_best_encoding(sys.stdout) + with open_stream(filename, "wb")[0] as f: + f.write(text.encode(encoding)) + try: + os.system('{} "{}"'.format(cmd, filename)) + finally: + os.unlink(filename) + + +def _nullpager(stream, generator, color): + """Simply print unformatted text. This is the ultimate fallback.""" + for text in generator: + if not color: + text = strip_ansi(text) + stream.write(text) + + +class Editor(object): + def __init__(self, editor=None, env=None, require_save=True, extension=".txt"): + self.editor = editor + self.env = env + self.require_save = require_save + self.extension = extension + + def get_editor(self): + if self.editor is not None: + return self.editor + for key in "VISUAL", "EDITOR": + rv = os.environ.get(key) + if rv: + return rv + if WIN: + return "notepad" + for editor in "sensible-editor", "vim", "nano": + if os.system("which {} >/dev/null 2>&1".format(editor)) == 0: + return editor + return "vi" + + def edit_file(self, filename): + import subprocess + + editor = self.get_editor() + if self.env: + environ = os.environ.copy() + environ.update(self.env) + else: + environ = None + try: + c = subprocess.Popen( + '{} "{}"'.format(editor, filename), env=environ, shell=True, + ) + exit_code = c.wait() + if exit_code != 0: + raise ClickException("{}: Editing failed!".format(editor)) + except OSError as e: + raise ClickException("{}: Editing failed: {}".format(editor, e)) + + def edit(self, text): + import tempfile + + text = text or "" + if text and not text.endswith("\n"): + text += "\n" + + fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension) + try: + if WIN: + encoding = "utf-8-sig" + text = text.replace("\n", "\r\n") + else: + encoding = "utf-8" + text = text.encode(encoding) + + f = os.fdopen(fd, "wb") + f.write(text) + f.close() + timestamp = os.path.getmtime(name) + + self.edit_file(name) + + if self.require_save and os.path.getmtime(name) == timestamp: + return None + + f = open(name, "rb") + try: + rv = f.read() + finally: + f.close() + return rv.decode("utf-8-sig").replace("\r\n", "\n") + finally: + os.unlink(name) + + +def open_url(url, wait=False, locate=False): + import subprocess + + def _unquote_file(url): + try: + import urllib + except ImportError: + import urllib + if url.startswith("file://"): + url = urllib.unquote(url[7:]) + return url + + if sys.platform == "darwin": + args = ["open"] + if wait: + args.append("-W") + if locate: + args.append("-R") + args.append(_unquote_file(url)) + null = open("/dev/null", "w") + try: + return subprocess.Popen(args, stderr=null).wait() + finally: + null.close() + elif WIN: + if locate: + url = _unquote_file(url) + args = 'explorer /select,"{}"'.format(_unquote_file(url.replace('"', ""))) + else: + args = 'start {} "" "{}"'.format( + "/WAIT" if wait else "", url.replace('"', "") + ) + return os.system(args) + elif CYGWIN: + if locate: + url = _unquote_file(url) + args = 'cygstart "{}"'.format(os.path.dirname(url).replace('"', "")) + else: + args = 'cygstart {} "{}"'.format("-w" if wait else "", url.replace('"', "")) + return os.system(args) + + try: + if locate: + url = os.path.dirname(_unquote_file(url)) or "." + else: + url = _unquote_file(url) + c = subprocess.Popen(["xdg-open", url]) + if wait: + return c.wait() + return 0 + except OSError: + if url.startswith(("http://", "https://")) and not locate and not wait: + import webbrowser + + webbrowser.open(url) + return 0 + return 1 + + +def _translate_ch_to_exc(ch): + if ch == u"\x03": + raise KeyboardInterrupt() + if ch == u"\x04" and not WIN: # Unix-like, Ctrl+D + raise EOFError() + if ch == u"\x1a" and WIN: # Windows, Ctrl+Z + raise EOFError() + + +if WIN: + import msvcrt + + @contextlib.contextmanager + def raw_terminal(): + yield + + def getchar(echo): + # The function `getch` will return a bytes object corresponding to + # the pressed character. Since Windows 10 build 1803, it will also + # return \x00 when called a second time after pressing a regular key. + # + # `getwch` does not share this probably-bugged behavior. Moreover, it + # returns a Unicode object by default, which is what we want. + # + # Either of these functions will return \x00 or \xe0 to indicate + # a special key, and you need to call the same function again to get + # the "rest" of the code. The fun part is that \u00e0 is + # "latin small letter a with grave", so if you type that on a French + # keyboard, you _also_ get a \xe0. + # E.g., consider the Up arrow. This returns \xe0 and then \x48. The + # resulting Unicode string reads as "a with grave" + "capital H". + # This is indistinguishable from when the user actually types + # "a with grave" and then "capital H". + # + # When \xe0 is returned, we assume it's part of a special-key sequence + # and call `getwch` again, but that means that when the user types + # the \u00e0 character, `getchar` doesn't return until a second + # character is typed. + # The alternative is returning immediately, but that would mess up + # cross-platform handling of arrow keys and others that start with + # \xe0. Another option is using `getch`, but then we can't reliably + # read non-ASCII characters, because return values of `getch` are + # limited to the current 8-bit codepage. + # + # Anyway, Click doesn't claim to do this Right(tm), and using `getwch` + # is doing the right thing in more situations than with `getch`. + if echo: + func = msvcrt.getwche + else: + func = msvcrt.getwch + + rv = func() + if rv in (u"\x00", u"\xe0"): + # \x00 and \xe0 are control characters that indicate special key, + # see above. + rv += func() + _translate_ch_to_exc(rv) + return rv + + +else: + import tty + import termios + + @contextlib.contextmanager + def raw_terminal(): + if not isatty(sys.stdin): + f = open("/dev/tty") + fd = f.fileno() + else: + fd = sys.stdin.fileno() + f = None + try: + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(fd) + yield fd + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + sys.stdout.flush() + if f is not None: + f.close() + except termios.error: + pass + + def getchar(echo): + with raw_terminal() as fd: + ch = os.read(fd, 32) + ch = ch.decode(get_best_encoding(sys.stdin), "replace") + if echo and isatty(sys.stdout): + sys.stdout.write(ch) + _translate_ch_to_exc(ch) + return ch diff --git a/robot/lib/python3.8/site-packages/click/_textwrap.py b/robot/lib/python3.8/site-packages/click/_textwrap.py new file mode 100644 index 0000000000000000000000000000000000000000..6959087b7f317841e16d0732b88d4240c364f24c --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/_textwrap.py @@ -0,0 +1,37 @@ +import textwrap +from contextlib import contextmanager + + +class TextWrapper(textwrap.TextWrapper): + def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width): + space_left = max(width - cur_len, 1) + + if self.break_long_words: + last = reversed_chunks[-1] + cut = last[:space_left] + res = last[space_left:] + cur_line.append(cut) + reversed_chunks[-1] = res + elif not cur_line: + cur_line.append(reversed_chunks.pop()) + + @contextmanager + def extra_indent(self, indent): + old_initial_indent = self.initial_indent + old_subsequent_indent = self.subsequent_indent + self.initial_indent += indent + self.subsequent_indent += indent + try: + yield + finally: + self.initial_indent = old_initial_indent + self.subsequent_indent = old_subsequent_indent + + def indent_only(self, text): + rv = [] + for idx, line in enumerate(text.splitlines()): + indent = self.initial_indent + if idx > 0: + indent = self.subsequent_indent + rv.append(indent + line) + return "\n".join(rv) diff --git a/robot/lib/python3.8/site-packages/click/_unicodefun.py b/robot/lib/python3.8/site-packages/click/_unicodefun.py new file mode 100644 index 0000000000000000000000000000000000000000..781c3652272630609c14da72f88d62315b68798a --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/_unicodefun.py @@ -0,0 +1,131 @@ +import codecs +import os +import sys + +from ._compat import PY2 + + +def _find_unicode_literals_frame(): + import __future__ + + if not hasattr(sys, "_getframe"): # not all Python implementations have it + return 0 + frm = sys._getframe(1) + idx = 1 + while frm is not None: + if frm.f_globals.get("__name__", "").startswith("click."): + frm = frm.f_back + idx += 1 + elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag: + return idx + else: + break + return 0 + + +def _check_for_unicode_literals(): + if not __debug__: + return + + from . import disable_unicode_literals_warning + + if not PY2 or disable_unicode_literals_warning: + return + bad_frame = _find_unicode_literals_frame() + if bad_frame <= 0: + return + from warnings import warn + + warn( + Warning( + "Click detected the use of the unicode_literals __future__" + " import. This is heavily discouraged because it can" + " introduce subtle bugs in your code. You should instead" + ' use explicit u"" literals for your unicode strings. For' + " more information see" + " https://click.palletsprojects.com/python3/" + ), + stacklevel=bad_frame, + ) + + +def _verify_python3_env(): + """Ensures that the environment is good for unicode on Python 3.""" + if PY2: + return + try: + import locale + + fs_enc = codecs.lookup(locale.getpreferredencoding()).name + except Exception: + fs_enc = "ascii" + if fs_enc != "ascii": + return + + extra = "" + if os.name == "posix": + import subprocess + + try: + rv = subprocess.Popen( + ["locale", "-a"], stdout=subprocess.PIPE, stderr=subprocess.PIPE + ).communicate()[0] + except OSError: + rv = b"" + good_locales = set() + has_c_utf8 = False + + # Make sure we're operating on text here. + if isinstance(rv, bytes): + rv = rv.decode("ascii", "replace") + + for line in rv.splitlines(): + locale = line.strip() + if locale.lower().endswith((".utf-8", ".utf8")): + good_locales.add(locale) + if locale.lower() in ("c.utf8", "c.utf-8"): + has_c_utf8 = True + + extra += "\n\n" + if not good_locales: + extra += ( + "Additional information: on this system no suitable" + " UTF-8 locales were discovered. This most likely" + " requires resolving by reconfiguring the locale" + " system." + ) + elif has_c_utf8: + extra += ( + "This system supports the C.UTF-8 locale which is" + " recommended. You might be able to resolve your issue" + " by exporting the following environment variables:\n\n" + " export LC_ALL=C.UTF-8\n" + " export LANG=C.UTF-8" + ) + else: + extra += ( + "This system lists a couple of UTF-8 supporting locales" + " that you can pick from. The following suitable" + " locales were discovered: {}".format(", ".join(sorted(good_locales))) + ) + + bad_locale = None + for locale in os.environ.get("LC_ALL"), os.environ.get("LANG"): + if locale and locale.lower().endswith((".utf-8", ".utf8")): + bad_locale = locale + if locale is not None: + break + if bad_locale is not None: + extra += ( + "\n\nClick discovered that you exported a UTF-8 locale" + " but the locale system could not pick up from it" + " because it does not exist. The exported locale is" + " '{}' but it is not supported".format(bad_locale) + ) + + raise RuntimeError( + "Click will abort further execution because Python 3 was" + " configured to use ASCII as encoding for the environment." + " Consult https://click.palletsprojects.com/python3/ for" + " mitigation steps.{}".format(extra) + ) diff --git a/robot/lib/python3.8/site-packages/click/_winconsole.py b/robot/lib/python3.8/site-packages/click/_winconsole.py new file mode 100644 index 0000000000000000000000000000000000000000..b6c4274af0e86151c60541dfefa85b955009205c --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/_winconsole.py @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- +# This module is based on the excellent work by Adam Bartoš who +# provided a lot of what went into the implementation here in +# the discussion to issue1602 in the Python bug tracker. +# +# There are some general differences in regards to how this works +# compared to the original patches as we do not need to patch +# the entire interpreter but just work in our little world of +# echo and prmopt. +import ctypes +import io +import os +import sys +import time +import zlib +from ctypes import byref +from ctypes import c_char +from ctypes import c_char_p +from ctypes import c_int +from ctypes import c_ssize_t +from ctypes import c_ulong +from ctypes import c_void_p +from ctypes import POINTER +from ctypes import py_object +from ctypes import windll +from ctypes import WinError +from ctypes import WINFUNCTYPE +from ctypes.wintypes import DWORD +from ctypes.wintypes import HANDLE +from ctypes.wintypes import LPCWSTR +from ctypes.wintypes import LPWSTR + +import msvcrt + +from ._compat import _NonClosingTextIOWrapper +from ._compat import PY2 +from ._compat import text_type + +try: + from ctypes import pythonapi + + PyObject_GetBuffer = pythonapi.PyObject_GetBuffer + PyBuffer_Release = pythonapi.PyBuffer_Release +except ImportError: + pythonapi = None + + +c_ssize_p = POINTER(c_ssize_t) + +kernel32 = windll.kernel32 +GetStdHandle = kernel32.GetStdHandle +ReadConsoleW = kernel32.ReadConsoleW +WriteConsoleW = kernel32.WriteConsoleW +GetConsoleMode = kernel32.GetConsoleMode +GetLastError = kernel32.GetLastError +GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32)) +CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( + ("CommandLineToArgvW", windll.shell32) +) +LocalFree = WINFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p)( + ("LocalFree", windll.kernel32) +) + + +STDIN_HANDLE = GetStdHandle(-10) +STDOUT_HANDLE = GetStdHandle(-11) +STDERR_HANDLE = GetStdHandle(-12) + + +PyBUF_SIMPLE = 0 +PyBUF_WRITABLE = 1 + +ERROR_SUCCESS = 0 +ERROR_NOT_ENOUGH_MEMORY = 8 +ERROR_OPERATION_ABORTED = 995 + +STDIN_FILENO = 0 +STDOUT_FILENO = 1 +STDERR_FILENO = 2 + +EOF = b"\x1a" +MAX_BYTES_WRITTEN = 32767 + + +class Py_buffer(ctypes.Structure): + _fields_ = [ + ("buf", c_void_p), + ("obj", py_object), + ("len", c_ssize_t), + ("itemsize", c_ssize_t), + ("readonly", c_int), + ("ndim", c_int), + ("format", c_char_p), + ("shape", c_ssize_p), + ("strides", c_ssize_p), + ("suboffsets", c_ssize_p), + ("internal", c_void_p), + ] + + if PY2: + _fields_.insert(-1, ("smalltable", c_ssize_t * 2)) + + +# On PyPy we cannot get buffers so our ability to operate here is +# serverly limited. +if pythonapi is None: + get_buffer = None +else: + + def get_buffer(obj, writable=False): + buf = Py_buffer() + flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE + PyObject_GetBuffer(py_object(obj), byref(buf), flags) + try: + buffer_type = c_char * buf.len + return buffer_type.from_address(buf.buf) + finally: + PyBuffer_Release(byref(buf)) + + +class _WindowsConsoleRawIOBase(io.RawIOBase): + def __init__(self, handle): + self.handle = handle + + def isatty(self): + io.RawIOBase.isatty(self) + return True + + +class _WindowsConsoleReader(_WindowsConsoleRawIOBase): + def readable(self): + return True + + def readinto(self, b): + bytes_to_be_read = len(b) + if not bytes_to_be_read: + return 0 + elif bytes_to_be_read % 2: + raise ValueError( + "cannot read odd number of bytes from UTF-16-LE encoded console" + ) + + buffer = get_buffer(b, writable=True) + code_units_to_be_read = bytes_to_be_read // 2 + code_units_read = c_ulong() + + rv = ReadConsoleW( + HANDLE(self.handle), + buffer, + code_units_to_be_read, + byref(code_units_read), + None, + ) + if GetLastError() == ERROR_OPERATION_ABORTED: + # wait for KeyboardInterrupt + time.sleep(0.1) + if not rv: + raise OSError("Windows error: {}".format(GetLastError())) + + if buffer[0] == EOF: + return 0 + return 2 * code_units_read.value + + +class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): + def writable(self): + return True + + @staticmethod + def _get_error_message(errno): + if errno == ERROR_SUCCESS: + return "ERROR_SUCCESS" + elif errno == ERROR_NOT_ENOUGH_MEMORY: + return "ERROR_NOT_ENOUGH_MEMORY" + return "Windows error {}".format(errno) + + def write(self, b): + bytes_to_be_written = len(b) + buf = get_buffer(b) + code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2 + code_units_written = c_ulong() + + WriteConsoleW( + HANDLE(self.handle), + buf, + code_units_to_be_written, + byref(code_units_written), + None, + ) + bytes_written = 2 * code_units_written.value + + if bytes_written == 0 and bytes_to_be_written > 0: + raise OSError(self._get_error_message(GetLastError())) + return bytes_written + + +class ConsoleStream(object): + def __init__(self, text_stream, byte_stream): + self._text_stream = text_stream + self.buffer = byte_stream + + @property + def name(self): + return self.buffer.name + + def write(self, x): + if isinstance(x, text_type): + return self._text_stream.write(x) + try: + self.flush() + except Exception: + pass + return self.buffer.write(x) + + def writelines(self, lines): + for line in lines: + self.write(line) + + def __getattr__(self, name): + return getattr(self._text_stream, name) + + def isatty(self): + return self.buffer.isatty() + + def __repr__(self): + return "".format( + self.name, self.encoding + ) + + +class WindowsChunkedWriter(object): + """ + Wraps a stream (such as stdout), acting as a transparent proxy for all + attribute access apart from method 'write()' which we wrap to write in + limited chunks due to a Windows limitation on binary console streams. + """ + + def __init__(self, wrapped): + # double-underscore everything to prevent clashes with names of + # attributes on the wrapped stream object. + self.__wrapped = wrapped + + def __getattr__(self, name): + return getattr(self.__wrapped, name) + + def write(self, text): + total_to_write = len(text) + written = 0 + + while written < total_to_write: + to_write = min(total_to_write - written, MAX_BYTES_WRITTEN) + self.__wrapped.write(text[written : written + to_write]) + written += to_write + + +_wrapped_std_streams = set() + + +def _wrap_std_stream(name): + # Python 2 & Windows 7 and below + if ( + PY2 + and sys.getwindowsversion()[:2] <= (6, 1) + and name not in _wrapped_std_streams + ): + setattr(sys, name, WindowsChunkedWriter(getattr(sys, name))) + _wrapped_std_streams.add(name) + + +def _get_text_stdin(buffer_stream): + text_stream = _NonClosingTextIOWrapper( + io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return ConsoleStream(text_stream, buffer_stream) + + +def _get_text_stdout(buffer_stream): + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return ConsoleStream(text_stream, buffer_stream) + + +def _get_text_stderr(buffer_stream): + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return ConsoleStream(text_stream, buffer_stream) + + +if PY2: + + def _hash_py_argv(): + return zlib.crc32("\x00".join(sys.argv[1:])) + + _initial_argv_hash = _hash_py_argv() + + def _get_windows_argv(): + argc = c_int(0) + argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc)) + if not argv_unicode: + raise WinError() + try: + argv = [argv_unicode[i] for i in range(0, argc.value)] + finally: + LocalFree(argv_unicode) + del argv_unicode + + if not hasattr(sys, "frozen"): + argv = argv[1:] + while len(argv) > 0: + arg = argv[0] + if not arg.startswith("-") or arg == "-": + break + argv = argv[1:] + if arg.startswith(("-c", "-m")): + break + + return argv[1:] + + +_stream_factories = { + 0: _get_text_stdin, + 1: _get_text_stdout, + 2: _get_text_stderr, +} + + +def _is_console(f): + if not hasattr(f, "fileno"): + return False + + try: + fileno = f.fileno() + except OSError: + return False + + handle = msvcrt.get_osfhandle(fileno) + return bool(GetConsoleMode(handle, byref(DWORD()))) + + +def _get_windows_console_stream(f, encoding, errors): + if ( + get_buffer is not None + and encoding in ("utf-16-le", None) + and errors in ("strict", None) + and _is_console(f) + ): + func = _stream_factories.get(f.fileno()) + if func is not None: + if not PY2: + f = getattr(f, "buffer", None) + if f is None: + return None + else: + # If we are on Python 2 we need to set the stream that we + # deal with to binary mode as otherwise the exercise if a + # bit moot. The same problems apply as for + # get_binary_stdin and friends from _compat. + msvcrt.setmode(f.fileno(), os.O_BINARY) + return func(f) diff --git a/robot/lib/python3.8/site-packages/click/core.py b/robot/lib/python3.8/site-packages/click/core.py new file mode 100644 index 0000000000000000000000000000000000000000..f58bf26d2f988e5b02e060b21863874ac87e1529 --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/core.py @@ -0,0 +1,2030 @@ +import errno +import inspect +import os +import sys +from contextlib import contextmanager +from functools import update_wrapper +from itertools import repeat + +from ._compat import isidentifier +from ._compat import iteritems +from ._compat import PY2 +from ._compat import string_types +from ._unicodefun import _check_for_unicode_literals +from ._unicodefun import _verify_python3_env +from .exceptions import Abort +from .exceptions import BadParameter +from .exceptions import ClickException +from .exceptions import Exit +from .exceptions import MissingParameter +from .exceptions import UsageError +from .formatting import HelpFormatter +from .formatting import join_options +from .globals import pop_context +from .globals import push_context +from .parser import OptionParser +from .parser import split_opt +from .termui import confirm +from .termui import prompt +from .termui import style +from .types import BOOL +from .types import convert_type +from .types import IntRange +from .utils import echo +from .utils import get_os_args +from .utils import make_default_short_help +from .utils import make_str +from .utils import PacifyFlushWrapper + +_missing = object() + +SUBCOMMAND_METAVAR = "COMMAND [ARGS]..." +SUBCOMMANDS_METAVAR = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." + +DEPRECATED_HELP_NOTICE = " (DEPRECATED)" +DEPRECATED_INVOKE_NOTICE = "DeprecationWarning: The command %(name)s is deprecated." + + +def _maybe_show_deprecated_notice(cmd): + if cmd.deprecated: + echo(style(DEPRECATED_INVOKE_NOTICE % {"name": cmd.name}, fg="red"), err=True) + + +def fast_exit(code): + """Exit without garbage collection, this speeds up exit by about 10ms for + things like bash completion. + """ + sys.stdout.flush() + sys.stderr.flush() + os._exit(code) + + +def _bashcomplete(cmd, prog_name, complete_var=None): + """Internal handler for the bash completion support.""" + if complete_var is None: + complete_var = "_{}_COMPLETE".format(prog_name.replace("-", "_").upper()) + complete_instr = os.environ.get(complete_var) + if not complete_instr: + return + + from ._bashcomplete import bashcomplete + + if bashcomplete(cmd, prog_name, complete_var, complete_instr): + fast_exit(1) + + +def _check_multicommand(base_command, cmd_name, cmd, register=False): + if not base_command.chain or not isinstance(cmd, MultiCommand): + return + if register: + hint = ( + "It is not possible to add multi commands as children to" + " another multi command that is in chain mode." + ) + else: + hint = ( + "Found a multi command as subcommand to a multi command" + " that is in chain mode. This is not supported." + ) + raise RuntimeError( + "{}. Command '{}' is set to chain and '{}' was added as" + " subcommand but it in itself is a multi command. ('{}' is a {}" + " within a chained {} named '{}').".format( + hint, + base_command.name, + cmd_name, + cmd_name, + cmd.__class__.__name__, + base_command.__class__.__name__, + base_command.name, + ) + ) + + +def batch(iterable, batch_size): + return list(zip(*repeat(iter(iterable), batch_size))) + + +def invoke_param_callback(callback, ctx, param, value): + code = getattr(callback, "__code__", None) + args = getattr(code, "co_argcount", 3) + + if args < 3: + from warnings import warn + + warn( + "Parameter callbacks take 3 args, (ctx, param, value). The" + " 2-arg style is deprecated and will be removed in 8.0.".format(callback), + DeprecationWarning, + stacklevel=3, + ) + return callback(ctx, value) + + return callback(ctx, param, value) + + +@contextmanager +def augment_usage_errors(ctx, param=None): + """Context manager that attaches extra information to exceptions.""" + try: + yield + except BadParameter as e: + if e.ctx is None: + e.ctx = ctx + if param is not None and e.param is None: + e.param = param + raise + except UsageError as e: + if e.ctx is None: + e.ctx = ctx + raise + + +def iter_params_for_processing(invocation_order, declaration_order): + """Given a sequence of parameters in the order as should be considered + for processing and an iterable of parameters that exist, this returns + a list in the correct order as they should be processed. + """ + + def sort_key(item): + try: + idx = invocation_order.index(item) + except ValueError: + idx = float("inf") + return (not item.is_eager, idx) + + return sorted(declaration_order, key=sort_key) + + +class Context(object): + """The context is a special internal object that holds state relevant + for the script execution at every single level. It's normally invisible + to commands unless they opt-in to getting access to it. + + The context is useful as it can pass internal objects around and can + control special execution features such as reading data from + environment variables. + + A context can be used as context manager in which case it will call + :meth:`close` on teardown. + + .. versionadded:: 2.0 + Added the `resilient_parsing`, `help_option_names`, + `token_normalize_func` parameters. + + .. versionadded:: 3.0 + Added the `allow_extra_args` and `allow_interspersed_args` + parameters. + + .. versionadded:: 4.0 + Added the `color`, `ignore_unknown_options`, and + `max_content_width` parameters. + + .. versionadded:: 7.1 + Added the `show_default` parameter. + + :param command: the command class for this context. + :param parent: the parent context. + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it is usually + the name of the script, for commands below it it's + the name of the script. + :param obj: an arbitrary object of user data. + :param auto_envvar_prefix: the prefix to use for automatic environment + variables. If this is `None` then reading + from environment variables is disabled. This + does not affect manually set environment + variables which are always read. + :param default_map: a dictionary (like object) with default values + for parameters. + :param terminal_width: the width of the terminal. The default is + inherit from parent context. If no context + defines the terminal width then auto + detection will be applied. + :param max_content_width: the maximum width for content rendered by + Click (this currently only affects help + pages). This defaults to 80 characters if + not overridden. In other words: even if the + terminal is larger than that, Click will not + format things wider than 80 characters by + default. In addition to that, formatters might + add some safety mapping on the right. + :param resilient_parsing: if this flag is enabled then Click will + parse without any interactivity or callback + invocation. Default values will also be + ignored. This is useful for implementing + things such as completion support. + :param allow_extra_args: if this is set to `True` then extra arguments + at the end will not raise an error and will be + kept on the context. The default is to inherit + from the command. + :param allow_interspersed_args: if this is set to `False` then options + and arguments cannot be mixed. The + default is to inherit from the command. + :param ignore_unknown_options: instructs click to ignore options it does + not know and keeps them for later + processing. + :param help_option_names: optionally a list of strings that define how + the default help parameter is named. The + default is ``['--help']``. + :param token_normalize_func: an optional function that is used to + normalize tokens (options, choices, + etc.). This for instance can be used to + implement case insensitive behavior. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are used in texts that Click prints which is by + default not the case. This for instance would affect + help output. + :param show_default: if True, shows defaults for all options. + Even if an option is later created with show_default=False, + this command-level setting overrides it. + """ + + def __init__( + self, + command, + parent=None, + info_name=None, + obj=None, + auto_envvar_prefix=None, + default_map=None, + terminal_width=None, + max_content_width=None, + resilient_parsing=False, + allow_extra_args=None, + allow_interspersed_args=None, + ignore_unknown_options=None, + help_option_names=None, + token_normalize_func=None, + color=None, + show_default=None, + ): + #: the parent context or `None` if none exists. + self.parent = parent + #: the :class:`Command` for this context. + self.command = command + #: the descriptive information name + self.info_name = info_name + #: the parsed parameters except if the value is hidden in which + #: case it's not remembered. + self.params = {} + #: the leftover arguments. + self.args = [] + #: protected arguments. These are arguments that are prepended + #: to `args` when certain parsing scenarios are encountered but + #: must be never propagated to another arguments. This is used + #: to implement nested parsing. + self.protected_args = [] + if obj is None and parent is not None: + obj = parent.obj + #: the user object stored. + self.obj = obj + self._meta = getattr(parent, "meta", {}) + + #: A dictionary (-like object) with defaults for parameters. + if ( + default_map is None + and parent is not None + and parent.default_map is not None + ): + default_map = parent.default_map.get(info_name) + self.default_map = default_map + + #: This flag indicates if a subcommand is going to be executed. A + #: group callback can use this information to figure out if it's + #: being executed directly or because the execution flow passes + #: onwards to a subcommand. By default it's None, but it can be + #: the name of the subcommand to execute. + #: + #: If chaining is enabled this will be set to ``'*'`` in case + #: any commands are executed. It is however not possible to + #: figure out which ones. If you require this knowledge you + #: should use a :func:`resultcallback`. + self.invoked_subcommand = None + + if terminal_width is None and parent is not None: + terminal_width = parent.terminal_width + #: The width of the terminal (None is autodetection). + self.terminal_width = terminal_width + + if max_content_width is None and parent is not None: + max_content_width = parent.max_content_width + #: The maximum width of formatted content (None implies a sensible + #: default which is 80 for most things). + self.max_content_width = max_content_width + + if allow_extra_args is None: + allow_extra_args = command.allow_extra_args + #: Indicates if the context allows extra args or if it should + #: fail on parsing. + #: + #: .. versionadded:: 3.0 + self.allow_extra_args = allow_extra_args + + if allow_interspersed_args is None: + allow_interspersed_args = command.allow_interspersed_args + #: Indicates if the context allows mixing of arguments and + #: options or not. + #: + #: .. versionadded:: 3.0 + self.allow_interspersed_args = allow_interspersed_args + + if ignore_unknown_options is None: + ignore_unknown_options = command.ignore_unknown_options + #: Instructs click to ignore options that a command does not + #: understand and will store it on the context for later + #: processing. This is primarily useful for situations where you + #: want to call into external programs. Generally this pattern is + #: strongly discouraged because it's not possibly to losslessly + #: forward all arguments. + #: + #: .. versionadded:: 4.0 + self.ignore_unknown_options = ignore_unknown_options + + if help_option_names is None: + if parent is not None: + help_option_names = parent.help_option_names + else: + help_option_names = ["--help"] + + #: The names for the help options. + self.help_option_names = help_option_names + + if token_normalize_func is None and parent is not None: + token_normalize_func = parent.token_normalize_func + + #: An optional normalization function for tokens. This is + #: options, choices, commands etc. + self.token_normalize_func = token_normalize_func + + #: Indicates if resilient parsing is enabled. In that case Click + #: will do its best to not cause any failures and default values + #: will be ignored. Useful for completion. + self.resilient_parsing = resilient_parsing + + # If there is no envvar prefix yet, but the parent has one and + # the command on this level has a name, we can expand the envvar + # prefix automatically. + if auto_envvar_prefix is None: + if ( + parent is not None + and parent.auto_envvar_prefix is not None + and self.info_name is not None + ): + auto_envvar_prefix = "{}_{}".format( + parent.auto_envvar_prefix, self.info_name.upper() + ) + else: + auto_envvar_prefix = auto_envvar_prefix.upper() + if auto_envvar_prefix is not None: + auto_envvar_prefix = auto_envvar_prefix.replace("-", "_") + self.auto_envvar_prefix = auto_envvar_prefix + + if color is None and parent is not None: + color = parent.color + + #: Controls if styling output is wanted or not. + self.color = color + + self.show_default = show_default + + self._close_callbacks = [] + self._depth = 0 + + def __enter__(self): + self._depth += 1 + push_context(self) + return self + + def __exit__(self, exc_type, exc_value, tb): + self._depth -= 1 + if self._depth == 0: + self.close() + pop_context() + + @contextmanager + def scope(self, cleanup=True): + """This helper method can be used with the context object to promote + it to the current thread local (see :func:`get_current_context`). + The default behavior of this is to invoke the cleanup functions which + can be disabled by setting `cleanup` to `False`. The cleanup + functions are typically used for things such as closing file handles. + + If the cleanup is intended the context object can also be directly + used as a context manager. + + Example usage:: + + with ctx.scope(): + assert get_current_context() is ctx + + This is equivalent:: + + with ctx: + assert get_current_context() is ctx + + .. versionadded:: 5.0 + + :param cleanup: controls if the cleanup functions should be run or + not. The default is to run these functions. In + some situations the context only wants to be + temporarily pushed in which case this can be disabled. + Nested pushes automatically defer the cleanup. + """ + if not cleanup: + self._depth += 1 + try: + with self as rv: + yield rv + finally: + if not cleanup: + self._depth -= 1 + + @property + def meta(self): + """This is a dictionary which is shared with all the contexts + that are nested. It exists so that click utilities can store some + state here if they need to. It is however the responsibility of + that code to manage this dictionary well. + + The keys are supposed to be unique dotted strings. For instance + module paths are a good choice for it. What is stored in there is + irrelevant for the operation of click. However what is important is + that code that places data here adheres to the general semantics of + the system. + + Example usage:: + + LANG_KEY = f'{__name__}.lang' + + def set_language(value): + ctx = get_current_context() + ctx.meta[LANG_KEY] = value + + def get_language(): + return get_current_context().meta.get(LANG_KEY, 'en_US') + + .. versionadded:: 5.0 + """ + return self._meta + + def make_formatter(self): + """Creates the formatter for the help and usage output.""" + return HelpFormatter( + width=self.terminal_width, max_width=self.max_content_width + ) + + def call_on_close(self, f): + """This decorator remembers a function as callback that should be + executed when the context tears down. This is most useful to bind + resource handling to the script execution. For instance, file objects + opened by the :class:`File` type will register their close callbacks + here. + + :param f: the function to execute on teardown. + """ + self._close_callbacks.append(f) + return f + + def close(self): + """Invokes all close callbacks.""" + for cb in self._close_callbacks: + cb() + self._close_callbacks = [] + + @property + def command_path(self): + """The computed command path. This is used for the ``usage`` + information on the help page. It's automatically created by + combining the info names of the chain of contexts to the root. + """ + rv = "" + if self.info_name is not None: + rv = self.info_name + if self.parent is not None: + rv = "{} {}".format(self.parent.command_path, rv) + return rv.lstrip() + + def find_root(self): + """Finds the outermost context.""" + node = self + while node.parent is not None: + node = node.parent + return node + + def find_object(self, object_type): + """Finds the closest object of a given type.""" + node = self + while node is not None: + if isinstance(node.obj, object_type): + return node.obj + node = node.parent + + def ensure_object(self, object_type): + """Like :meth:`find_object` but sets the innermost object to a + new instance of `object_type` if it does not exist. + """ + rv = self.find_object(object_type) + if rv is None: + self.obj = rv = object_type() + return rv + + def lookup_default(self, name): + """Looks up the default for a parameter name. This by default + looks into the :attr:`default_map` if available. + """ + if self.default_map is not None: + rv = self.default_map.get(name) + if callable(rv): + rv = rv() + return rv + + def fail(self, message): + """Aborts the execution of the program with a specific error + message. + + :param message: the error message to fail with. + """ + raise UsageError(message, self) + + def abort(self): + """Aborts the script.""" + raise Abort() + + def exit(self, code=0): + """Exits the application with a given exit code.""" + raise Exit(code) + + def get_usage(self): + """Helper method to get formatted usage string for the current + context and command. + """ + return self.command.get_usage(self) + + def get_help(self): + """Helper method to get formatted help page for the current + context and command. + """ + return self.command.get_help(self) + + def invoke(*args, **kwargs): # noqa: B902 + """Invokes a command callback in exactly the way it expects. There + are two ways to invoke this method: + + 1. the first argument can be a callback and all other arguments and + keyword arguments are forwarded directly to the function. + 2. the first argument is a click command object. In that case all + arguments are forwarded as well but proper click parameters + (options and click arguments) must be keyword arguments and Click + will fill in defaults. + + Note that before Click 3.2 keyword arguments were not properly filled + in against the intention of this code and no context was created. For + more information about this change and why it was done in a bugfix + release see :ref:`upgrade-to-3.2`. + """ + self, callback = args[:2] + ctx = self + + # It's also possible to invoke another command which might or + # might not have a callback. In that case we also fill + # in defaults and make a new context for this command. + if isinstance(callback, Command): + other_cmd = callback + callback = other_cmd.callback + ctx = Context(other_cmd, info_name=other_cmd.name, parent=self) + if callback is None: + raise TypeError( + "The given command does not have a callback that can be invoked." + ) + + for param in other_cmd.params: + if param.name not in kwargs and param.expose_value: + kwargs[param.name] = param.get_default(ctx) + + args = args[2:] + with augment_usage_errors(self): + with ctx: + return callback(*args, **kwargs) + + def forward(*args, **kwargs): # noqa: B902 + """Similar to :meth:`invoke` but fills in default keyword + arguments from the current context if the other command expects + it. This cannot invoke callbacks directly, only other commands. + """ + self, cmd = args[:2] + + # It's also possible to invoke another command which might or + # might not have a callback. + if not isinstance(cmd, Command): + raise TypeError("Callback is not a command.") + + for param in self.params: + if param not in kwargs: + kwargs[param] = self.params[param] + + return self.invoke(cmd, **kwargs) + + +class BaseCommand(object): + """The base command implements the minimal API contract of commands. + Most code will never use this as it does not implement a lot of useful + functionality but it can act as the direct subclass of alternative + parsing methods that do not depend on the Click parser. + + For instance, this can be used to bridge Click and other systems like + argparse or docopt. + + Because base commands do not implement a lot of the API that other + parts of Click take for granted, they are not supported for all + operations. For instance, they cannot be used with the decorators + usually and they have no built-in callback system. + + .. versionchanged:: 2.0 + Added the `context_settings` parameter. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + """ + + #: the default for the :attr:`Context.allow_extra_args` flag. + allow_extra_args = False + #: the default for the :attr:`Context.allow_interspersed_args` flag. + allow_interspersed_args = True + #: the default for the :attr:`Context.ignore_unknown_options` flag. + ignore_unknown_options = False + + def __init__(self, name, context_settings=None): + #: the name the command thinks it has. Upon registering a command + #: on a :class:`Group` the group will default the command name + #: with this information. You should instead use the + #: :class:`Context`\'s :attr:`~Context.info_name` attribute. + self.name = name + if context_settings is None: + context_settings = {} + #: an optional dictionary with defaults passed to the context. + self.context_settings = context_settings + + def __repr__(self): + return "<{} {}>".format(self.__class__.__name__, self.name) + + def get_usage(self, ctx): + raise NotImplementedError("Base commands cannot get usage") + + def get_help(self, ctx): + raise NotImplementedError("Base commands cannot get help") + + def make_context(self, info_name, args, parent=None, **extra): + """This function when given an info name and arguments will kick + off the parsing and create a new :class:`Context`. It does not + invoke the actual command callback though. + + :param info_name: the info name for this invokation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it's usually + the name of the script, for commands below it it's + the name of the script. + :param args: the arguments to parse as list of strings. + :param parent: the parent context if available. + :param extra: extra keyword arguments forwarded to the context + constructor. + """ + for key, value in iteritems(self.context_settings): + if key not in extra: + extra[key] = value + ctx = Context(self, info_name=info_name, parent=parent, **extra) + with ctx.scope(cleanup=False): + self.parse_args(ctx, args) + return ctx + + def parse_args(self, ctx, args): + """Given a context and a list of arguments this creates the parser + and parses the arguments, then modifies the context as necessary. + This is automatically invoked by :meth:`make_context`. + """ + raise NotImplementedError("Base commands do not know how to parse arguments.") + + def invoke(self, ctx): + """Given a context, this invokes the command. The default + implementation is raising a not implemented error. + """ + raise NotImplementedError("Base commands are not invokable by default") + + def main( + self, + args=None, + prog_name=None, + complete_var=None, + standalone_mode=True, + **extra + ): + """This is the way to invoke a script with all the bells and + whistles as a command line application. This will always terminate + the application after a call. If this is not wanted, ``SystemExit`` + needs to be caught. + + This method is also available by directly calling the instance of + a :class:`Command`. + + .. versionadded:: 3.0 + Added the `standalone_mode` flag to control the standalone mode. + + :param args: the arguments that should be used for parsing. If not + provided, ``sys.argv[1:]`` is used. + :param prog_name: the program name that should be used. By default + the program name is constructed by taking the file + name from ``sys.argv[0]``. + :param complete_var: the environment variable that controls the + bash completion support. The default is + ``"__COMPLETE"`` with prog_name in + uppercase. + :param standalone_mode: the default behavior is to invoke the script + in standalone mode. Click will then + handle exceptions and convert them into + error messages and the function will never + return but shut down the interpreter. If + this is set to `False` they will be + propagated to the caller and the return + value of this function is the return value + of :meth:`invoke`. + :param extra: extra keyword arguments are forwarded to the context + constructor. See :class:`Context` for more information. + """ + # If we are in Python 3, we will verify that the environment is + # sane at this point or reject further execution to avoid a + # broken script. + if not PY2: + _verify_python3_env() + else: + _check_for_unicode_literals() + + if args is None: + args = get_os_args() + else: + args = list(args) + + if prog_name is None: + prog_name = make_str( + os.path.basename(sys.argv[0] if sys.argv else __file__) + ) + + # Hook for the Bash completion. This only activates if the Bash + # completion is actually enabled, otherwise this is quite a fast + # noop. + _bashcomplete(self, prog_name, complete_var) + + try: + try: + with self.make_context(prog_name, args, **extra) as ctx: + rv = self.invoke(ctx) + if not standalone_mode: + return rv + # it's not safe to `ctx.exit(rv)` here! + # note that `rv` may actually contain data like "1" which + # has obvious effects + # more subtle case: `rv=[None, None]` can come out of + # chained commands which all returned `None` -- so it's not + # even always obvious that `rv` indicates success/failure + # by its truthiness/falsiness + ctx.exit() + except (EOFError, KeyboardInterrupt): + echo(file=sys.stderr) + raise Abort() + except ClickException as e: + if not standalone_mode: + raise + e.show() + sys.exit(e.exit_code) + except IOError as e: + if e.errno == errno.EPIPE: + sys.stdout = PacifyFlushWrapper(sys.stdout) + sys.stderr = PacifyFlushWrapper(sys.stderr) + sys.exit(1) + else: + raise + except Exit as e: + if standalone_mode: + sys.exit(e.exit_code) + else: + # in non-standalone mode, return the exit code + # note that this is only reached if `self.invoke` above raises + # an Exit explicitly -- thus bypassing the check there which + # would return its result + # the results of non-standalone execution may therefore be + # somewhat ambiguous: if there are codepaths which lead to + # `ctx.exit(1)` and to `return 1`, the caller won't be able to + # tell the difference between the two + return e.exit_code + except Abort: + if not standalone_mode: + raise + echo("Aborted!", file=sys.stderr) + sys.exit(1) + + def __call__(self, *args, **kwargs): + """Alias for :meth:`main`.""" + return self.main(*args, **kwargs) + + +class Command(BaseCommand): + """Commands are the basic building block of command line interfaces in + Click. A basic command handles command line parsing and might dispatch + more parsing to commands nested below it. + + .. versionchanged:: 2.0 + Added the `context_settings` parameter. + .. versionchanged:: 7.1 + Added the `no_args_is_help` parameter. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + :param callback: the callback to invoke. This is optional. + :param params: the parameters to register with this command. This can + be either :class:`Option` or :class:`Argument` objects. + :param help: the help string to use for this command. + :param epilog: like the help string but it's printed at the end of the + help page after everything else. + :param short_help: the short help to use for this command. This is + shown on the command listing of the parent command. + :param add_help_option: by default each command registers a ``--help`` + option. This can be disabled by this parameter. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is disabled by default. + If enabled this will add ``--help`` as argument + if no arguments are passed + :param hidden: hide this command from help outputs. + + :param deprecated: issues a message indicating that + the command is deprecated. + """ + + def __init__( + self, + name, + context_settings=None, + callback=None, + params=None, + help=None, + epilog=None, + short_help=None, + options_metavar="[OPTIONS]", + add_help_option=True, + no_args_is_help=False, + hidden=False, + deprecated=False, + ): + BaseCommand.__init__(self, name, context_settings) + #: the callback to execute when the command fires. This might be + #: `None` in which case nothing happens. + self.callback = callback + #: the list of parameters for this command in the order they + #: should show up in the help page and execute. Eager parameters + #: will automatically be handled before non eager ones. + self.params = params or [] + # if a form feed (page break) is found in the help text, truncate help + # text to the content preceding the first form feed + if help and "\f" in help: + help = help.split("\f", 1)[0] + self.help = help + self.epilog = epilog + self.options_metavar = options_metavar + self.short_help = short_help + self.add_help_option = add_help_option + self.no_args_is_help = no_args_is_help + self.hidden = hidden + self.deprecated = deprecated + + def get_usage(self, ctx): + """Formats the usage line into a string and returns it. + + Calls :meth:`format_usage` internally. + """ + formatter = ctx.make_formatter() + self.format_usage(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_params(self, ctx): + rv = self.params + help_option = self.get_help_option(ctx) + if help_option is not None: + rv = rv + [help_option] + return rv + + def format_usage(self, ctx, formatter): + """Writes the usage line into the formatter. + + This is a low-level method called by :meth:`get_usage`. + """ + pieces = self.collect_usage_pieces(ctx) + formatter.write_usage(ctx.command_path, " ".join(pieces)) + + def collect_usage_pieces(self, ctx): + """Returns all the pieces that go into the usage line and returns + it as a list of strings. + """ + rv = [self.options_metavar] + for param in self.get_params(ctx): + rv.extend(param.get_usage_pieces(ctx)) + return rv + + def get_help_option_names(self, ctx): + """Returns the names for the help option.""" + all_names = set(ctx.help_option_names) + for param in self.params: + all_names.difference_update(param.opts) + all_names.difference_update(param.secondary_opts) + return all_names + + def get_help_option(self, ctx): + """Returns the help option object.""" + help_options = self.get_help_option_names(ctx) + if not help_options or not self.add_help_option: + return + + def show_help(ctx, param, value): + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + return Option( + help_options, + is_flag=True, + is_eager=True, + expose_value=False, + callback=show_help, + help="Show this message and exit.", + ) + + def make_parser(self, ctx): + """Creates the underlying option parser for this command.""" + parser = OptionParser(ctx) + for param in self.get_params(ctx): + param.add_to_parser(parser, ctx) + return parser + + def get_help(self, ctx): + """Formats the help into a string and returns it. + + Calls :meth:`format_help` internally. + """ + formatter = ctx.make_formatter() + self.format_help(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_short_help_str(self, limit=45): + """Gets short help for the command or makes it by shortening the + long help string. + """ + return ( + self.short_help + or self.help + and make_default_short_help(self.help, limit) + or "" + ) + + def format_help(self, ctx, formatter): + """Writes the help into the formatter if it exists. + + This is a low-level method called by :meth:`get_help`. + + This calls the following methods: + + - :meth:`format_usage` + - :meth:`format_help_text` + - :meth:`format_options` + - :meth:`format_epilog` + """ + self.format_usage(ctx, formatter) + self.format_help_text(ctx, formatter) + self.format_options(ctx, formatter) + self.format_epilog(ctx, formatter) + + def format_help_text(self, ctx, formatter): + """Writes the help text to the formatter if it exists.""" + if self.help: + formatter.write_paragraph() + with formatter.indentation(): + help_text = self.help + if self.deprecated: + help_text += DEPRECATED_HELP_NOTICE + formatter.write_text(help_text) + elif self.deprecated: + formatter.write_paragraph() + with formatter.indentation(): + formatter.write_text(DEPRECATED_HELP_NOTICE) + + def format_options(self, ctx, formatter): + """Writes all the options into the formatter if they exist.""" + opts = [] + for param in self.get_params(ctx): + rv = param.get_help_record(ctx) + if rv is not None: + opts.append(rv) + + if opts: + with formatter.section("Options"): + formatter.write_dl(opts) + + def format_epilog(self, ctx, formatter): + """Writes the epilog into the formatter if it exists.""" + if self.epilog: + formatter.write_paragraph() + with formatter.indentation(): + formatter.write_text(self.epilog) + + def parse_args(self, ctx, args): + if not args and self.no_args_is_help and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + parser = self.make_parser(ctx) + opts, args, param_order = parser.parse_args(args=args) + + for param in iter_params_for_processing(param_order, self.get_params(ctx)): + value, args = param.handle_parse_result(ctx, opts, args) + + if args and not ctx.allow_extra_args and not ctx.resilient_parsing: + ctx.fail( + "Got unexpected extra argument{} ({})".format( + "s" if len(args) != 1 else "", " ".join(map(make_str, args)) + ) + ) + + ctx.args = args + return args + + def invoke(self, ctx): + """Given a context, this invokes the attached callback (if it exists) + in the right way. + """ + _maybe_show_deprecated_notice(self) + if self.callback is not None: + return ctx.invoke(self.callback, **ctx.params) + + +class MultiCommand(Command): + """A multi command is the basic implementation of a command that + dispatches to subcommands. The most common version is the + :class:`Group`. + + :param invoke_without_command: this controls how the multi command itself + is invoked. By default it's only invoked + if a subcommand is provided. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is enabled by default if + `invoke_without_command` is disabled or disabled + if it's enabled. If enabled this will add + ``--help`` as argument if no arguments are + passed. + :param subcommand_metavar: the string that is used in the documentation + to indicate the subcommand place. + :param chain: if this is set to `True` chaining of multiple subcommands + is enabled. This restricts the form of commands in that + they cannot have optional arguments but it allows + multiple commands to be chained together. + :param result_callback: the result callback to attach to this multi + command. + """ + + allow_extra_args = True + allow_interspersed_args = False + + def __init__( + self, + name=None, + invoke_without_command=False, + no_args_is_help=None, + subcommand_metavar=None, + chain=False, + result_callback=None, + **attrs + ): + Command.__init__(self, name, **attrs) + if no_args_is_help is None: + no_args_is_help = not invoke_without_command + self.no_args_is_help = no_args_is_help + self.invoke_without_command = invoke_without_command + if subcommand_metavar is None: + if chain: + subcommand_metavar = SUBCOMMANDS_METAVAR + else: + subcommand_metavar = SUBCOMMAND_METAVAR + self.subcommand_metavar = subcommand_metavar + self.chain = chain + #: The result callback that is stored. This can be set or + #: overridden with the :func:`resultcallback` decorator. + self.result_callback = result_callback + + if self.chain: + for param in self.params: + if isinstance(param, Argument) and not param.required: + raise RuntimeError( + "Multi commands in chain mode cannot have" + " optional arguments." + ) + + def collect_usage_pieces(self, ctx): + rv = Command.collect_usage_pieces(self, ctx) + rv.append(self.subcommand_metavar) + return rv + + def format_options(self, ctx, formatter): + Command.format_options(self, ctx, formatter) + self.format_commands(ctx, formatter) + + def resultcallback(self, replace=False): + """Adds a result callback to the chain command. By default if a + result callback is already registered this will chain them but + this can be disabled with the `replace` parameter. The result + callback is invoked with the return value of the subcommand + (or the list of return values from all subcommands if chaining + is enabled) as well as the parameters as they would be passed + to the main callback. + + Example:: + + @click.group() + @click.option('-i', '--input', default=23) + def cli(input): + return 42 + + @cli.resultcallback() + def process_result(result, input): + return result + input + + .. versionadded:: 3.0 + + :param replace: if set to `True` an already existing result + callback will be removed. + """ + + def decorator(f): + old_callback = self.result_callback + if old_callback is None or replace: + self.result_callback = f + return f + + def function(__value, *args, **kwargs): + return f(old_callback(__value, *args, **kwargs), *args, **kwargs) + + self.result_callback = rv = update_wrapper(function, f) + return rv + + return decorator + + def format_commands(self, ctx, formatter): + """Extra format methods for multi methods that adds all the commands + after the options. + """ + commands = [] + for subcommand in self.list_commands(ctx): + cmd = self.get_command(ctx, subcommand) + # What is this, the tool lied about a command. Ignore it + if cmd is None: + continue + if cmd.hidden: + continue + + commands.append((subcommand, cmd)) + + # allow for 3 times the default spacing + if len(commands): + limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands) + + rows = [] + for subcommand, cmd in commands: + help = cmd.get_short_help_str(limit) + rows.append((subcommand, help)) + + if rows: + with formatter.section("Commands"): + formatter.write_dl(rows) + + def parse_args(self, ctx, args): + if not args and self.no_args_is_help and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + rest = Command.parse_args(self, ctx, args) + if self.chain: + ctx.protected_args = rest + ctx.args = [] + elif rest: + ctx.protected_args, ctx.args = rest[:1], rest[1:] + + return ctx.args + + def invoke(self, ctx): + def _process_result(value): + if self.result_callback is not None: + value = ctx.invoke(self.result_callback, value, **ctx.params) + return value + + if not ctx.protected_args: + # If we are invoked without command the chain flag controls + # how this happens. If we are not in chain mode, the return + # value here is the return value of the command. + # If however we are in chain mode, the return value is the + # return value of the result processor invoked with an empty + # list (which means that no subcommand actually was executed). + if self.invoke_without_command: + if not self.chain: + return Command.invoke(self, ctx) + with ctx: + Command.invoke(self, ctx) + return _process_result([]) + ctx.fail("Missing command.") + + # Fetch args back out + args = ctx.protected_args + ctx.args + ctx.args = [] + ctx.protected_args = [] + + # If we're not in chain mode, we only allow the invocation of a + # single command but we also inform the current context about the + # name of the command to invoke. + if not self.chain: + # Make sure the context is entered so we do not clean up + # resources until the result processor has worked. + with ctx: + cmd_name, cmd, args = self.resolve_command(ctx, args) + ctx.invoked_subcommand = cmd_name + Command.invoke(self, ctx) + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) + with sub_ctx: + return _process_result(sub_ctx.command.invoke(sub_ctx)) + + # In chain mode we create the contexts step by step, but after the + # base command has been invoked. Because at that point we do not + # know the subcommands yet, the invoked subcommand attribute is + # set to ``*`` to inform the command that subcommands are executed + # but nothing else. + with ctx: + ctx.invoked_subcommand = "*" if args else None + Command.invoke(self, ctx) + + # Otherwise we make every single context and invoke them in a + # chain. In that case the return value to the result processor + # is the list of all invoked subcommand's results. + contexts = [] + while args: + cmd_name, cmd, args = self.resolve_command(ctx, args) + sub_ctx = cmd.make_context( + cmd_name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + ) + contexts.append(sub_ctx) + args, sub_ctx.args = sub_ctx.args, [] + + rv = [] + for sub_ctx in contexts: + with sub_ctx: + rv.append(sub_ctx.command.invoke(sub_ctx)) + return _process_result(rv) + + def resolve_command(self, ctx, args): + cmd_name = make_str(args[0]) + original_cmd_name = cmd_name + + # Get the command + cmd = self.get_command(ctx, cmd_name) + + # If we can't find the command but there is a normalization + # function available, we try with that one. + if cmd is None and ctx.token_normalize_func is not None: + cmd_name = ctx.token_normalize_func(cmd_name) + cmd = self.get_command(ctx, cmd_name) + + # If we don't find the command we want to show an error message + # to the user that it was not provided. However, there is + # something else we should do: if the first argument looks like + # an option we want to kick off parsing again for arguments to + # resolve things like --help which now should go to the main + # place. + if cmd is None and not ctx.resilient_parsing: + if split_opt(cmd_name)[0]: + self.parse_args(ctx, ctx.args) + ctx.fail("No such command '{}'.".format(original_cmd_name)) + + return cmd_name, cmd, args[1:] + + def get_command(self, ctx, cmd_name): + """Given a context and a command name, this returns a + :class:`Command` object if it exists or returns `None`. + """ + raise NotImplementedError() + + def list_commands(self, ctx): + """Returns a list of subcommand names in the order they should + appear. + """ + return [] + + +class Group(MultiCommand): + """A group allows a command to have subcommands attached. This is the + most common way to implement nesting in Click. + + :param commands: a dictionary of commands. + """ + + def __init__(self, name=None, commands=None, **attrs): + MultiCommand.__init__(self, name, **attrs) + #: the registered subcommands by their exported names. + self.commands = commands or {} + + def add_command(self, cmd, name=None): + """Registers another :class:`Command` with this group. If the name + is not provided, the name of the command is used. + """ + name = name or cmd.name + if name is None: + raise TypeError("Command has no name.") + _check_multicommand(self, name, cmd, register=True) + self.commands[name] = cmd + + def command(self, *args, **kwargs): + """A shortcut decorator for declaring and attaching a command to + the group. This takes the same arguments as :func:`command` but + immediately registers the created command with this instance by + calling into :meth:`add_command`. + """ + from .decorators import command + + def decorator(f): + cmd = command(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + return decorator + + def group(self, *args, **kwargs): + """A shortcut decorator for declaring and attaching a group to + the group. This takes the same arguments as :func:`group` but + immediately registers the created command with this instance by + calling into :meth:`add_command`. + """ + from .decorators import group + + def decorator(f): + cmd = group(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + return decorator + + def get_command(self, ctx, cmd_name): + return self.commands.get(cmd_name) + + def list_commands(self, ctx): + return sorted(self.commands) + + +class CommandCollection(MultiCommand): + """A command collection is a multi command that merges multiple multi + commands together into one. This is a straightforward implementation + that accepts a list of different multi commands as sources and + provides all the commands for each of them. + """ + + def __init__(self, name=None, sources=None, **attrs): + MultiCommand.__init__(self, name, **attrs) + #: The list of registered multi commands. + self.sources = sources or [] + + def add_source(self, multi_cmd): + """Adds a new multi command to the chain dispatcher.""" + self.sources.append(multi_cmd) + + def get_command(self, ctx, cmd_name): + for source in self.sources: + rv = source.get_command(ctx, cmd_name) + if rv is not None: + if self.chain: + _check_multicommand(self, cmd_name, rv) + return rv + + def list_commands(self, ctx): + rv = set() + for source in self.sources: + rv.update(source.list_commands(ctx)) + return sorted(rv) + + +class Parameter(object): + r"""A parameter to a command comes in two versions: they are either + :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently + not supported by design as some of the internals for parsing are + intentionally not finalized. + + Some settings are supported by both options and arguments. + + :param param_decls: the parameter declarations for this option or + argument. This is a list of flags or argument + names. + :param type: the type that should be used. Either a :class:`ParamType` + or a Python type. The later is converted into the former + automatically if supported. + :param required: controls if this is optional or not. + :param default: the default value if omitted. This can also be a callable, + in which case it's invoked when the default is needed + without any arguments. + :param callback: a callback that should be executed after the parameter + was matched. This is called as ``fn(ctx, param, + value)`` and needs to return the value. + :param nargs: the number of arguments to match. If not ``1`` the return + value is a tuple instead of single value. The default for + nargs is ``1`` (except if the type is a tuple, then it's + the arity of the tuple). + :param metavar: how the value is represented in the help page. + :param expose_value: if this is `True` then the value is passed onwards + to the command callback and stored on the context, + otherwise it's skipped. + :param is_eager: eager values are processed before non eager ones. This + should not be set for arguments or it will inverse the + order of processing. + :param envvar: a string or list of strings that are environment variables + that should be checked. + + .. versionchanged:: 7.1 + Empty environment variables are ignored rather than taking the + empty string value. This makes it possible for scripts to clear + variables if they can't unset them. + + .. versionchanged:: 2.0 + Changed signature for parameter callback to also be passed the + parameter. The old callback format will still work, but it will + raise a warning to give you a chance to migrate the code easier. + """ + param_type_name = "parameter" + + def __init__( + self, + param_decls=None, + type=None, + required=False, + default=None, + callback=None, + nargs=None, + metavar=None, + expose_value=True, + is_eager=False, + envvar=None, + autocompletion=None, + ): + self.name, self.opts, self.secondary_opts = self._parse_decls( + param_decls or (), expose_value + ) + + self.type = convert_type(type, default) + + # Default nargs to what the type tells us if we have that + # information available. + if nargs is None: + if self.type.is_composite: + nargs = self.type.arity + else: + nargs = 1 + + self.required = required + self.callback = callback + self.nargs = nargs + self.multiple = False + self.expose_value = expose_value + self.default = default + self.is_eager = is_eager + self.metavar = metavar + self.envvar = envvar + self.autocompletion = autocompletion + + def __repr__(self): + return "<{} {}>".format(self.__class__.__name__, self.name) + + @property + def human_readable_name(self): + """Returns the human readable name of this parameter. This is the + same as the name for options, but the metavar for arguments. + """ + return self.name + + def make_metavar(self): + if self.metavar is not None: + return self.metavar + metavar = self.type.get_metavar(self) + if metavar is None: + metavar = self.type.name.upper() + if self.nargs != 1: + metavar += "..." + return metavar + + def get_default(self, ctx): + """Given a context variable this calculates the default value.""" + # Otherwise go with the regular default. + if callable(self.default): + rv = self.default() + else: + rv = self.default + return self.type_cast_value(ctx, rv) + + def add_to_parser(self, parser, ctx): + pass + + def consume_value(self, ctx, opts): + value = opts.get(self.name) + if value is None: + value = self.value_from_envvar(ctx) + if value is None: + value = ctx.lookup_default(self.name) + return value + + def type_cast_value(self, ctx, value): + """Given a value this runs it properly through the type system. + This automatically handles things like `nargs` and `multiple` as + well as composite types. + """ + if self.type.is_composite: + if self.nargs <= 1: + raise TypeError( + "Attempted to invoke composite type but nargs has" + " been set to {}. This is not supported; nargs" + " needs to be set to a fixed value > 1.".format(self.nargs) + ) + if self.multiple: + return tuple(self.type(x or (), self, ctx) for x in value or ()) + return self.type(value or (), self, ctx) + + def _convert(value, level): + if level == 0: + return self.type(value, self, ctx) + return tuple(_convert(x, level - 1) for x in value or ()) + + return _convert(value, (self.nargs != 1) + bool(self.multiple)) + + def process_value(self, ctx, value): + """Given a value and context this runs the logic to convert the + value as necessary. + """ + # If the value we were given is None we do nothing. This way + # code that calls this can easily figure out if something was + # not provided. Otherwise it would be converted into an empty + # tuple for multiple invocations which is inconvenient. + if value is not None: + return self.type_cast_value(ctx, value) + + def value_is_missing(self, value): + if value is None: + return True + if (self.nargs != 1 or self.multiple) and value == (): + return True + return False + + def full_process_value(self, ctx, value): + value = self.process_value(ctx, value) + + if value is None and not ctx.resilient_parsing: + value = self.get_default(ctx) + + if self.required and self.value_is_missing(value): + raise MissingParameter(ctx=ctx, param=self) + + return value + + def resolve_envvar_value(self, ctx): + if self.envvar is None: + return + if isinstance(self.envvar, (tuple, list)): + for envvar in self.envvar: + rv = os.environ.get(envvar) + if rv is not None: + return rv + else: + rv = os.environ.get(self.envvar) + + if rv != "": + return rv + + def value_from_envvar(self, ctx): + rv = self.resolve_envvar_value(ctx) + if rv is not None and self.nargs != 1: + rv = self.type.split_envvar_value(rv) + return rv + + def handle_parse_result(self, ctx, opts, args): + with augment_usage_errors(ctx, param=self): + value = self.consume_value(ctx, opts) + try: + value = self.full_process_value(ctx, value) + except Exception: + if not ctx.resilient_parsing: + raise + value = None + if self.callback is not None: + try: + value = invoke_param_callback(self.callback, ctx, self, value) + except Exception: + if not ctx.resilient_parsing: + raise + + if self.expose_value: + ctx.params[self.name] = value + return value, args + + def get_help_record(self, ctx): + pass + + def get_usage_pieces(self, ctx): + return [] + + def get_error_hint(self, ctx): + """Get a stringified version of the param for use in error messages to + indicate which param caused the error. + """ + hint_list = self.opts or [self.human_readable_name] + return " / ".join(repr(x) for x in hint_list) + + +class Option(Parameter): + """Options are usually optional values on the command line and + have some extra features that arguments don't have. + + All other parameters are passed onwards to the parameter constructor. + + :param show_default: controls if the default value should be shown on the + help page. Normally, defaults are not shown. If this + value is a string, it shows the string instead of the + value. This is particularly useful for dynamic options. + :param show_envvar: controls if an environment variable should be shown on + the help page. Normally, environment variables + are not shown. + :param prompt: if set to `True` or a non empty string then the user will be + prompted for input. If set to `True` the prompt will be the + option name capitalized. + :param confirmation_prompt: if set then the value will need to be confirmed + if it was prompted for. + :param hide_input: if this is `True` then the input on the prompt will be + hidden from the user. This is useful for password + input. + :param is_flag: forces this option to act as a flag. The default is + auto detection. + :param flag_value: which value should be used for this flag if it's + enabled. This is set to a boolean automatically if + the option string contains a slash to mark two options. + :param multiple: if this is set to `True` then the argument is accepted + multiple times and recorded. This is similar to ``nargs`` + in how it works but supports arbitrary number of + arguments. + :param count: this flag makes an option increment an integer. + :param allow_from_autoenv: if this is enabled then the value of this + parameter will be pulled from an environment + variable in case a prefix is defined on the + context. + :param help: the help string. + :param hidden: hide this option from help outputs. + """ + + param_type_name = "option" + + def __init__( + self, + param_decls=None, + show_default=False, + prompt=False, + confirmation_prompt=False, + hide_input=False, + is_flag=None, + flag_value=None, + multiple=False, + count=False, + allow_from_autoenv=True, + type=None, + help=None, + hidden=False, + show_choices=True, + show_envvar=False, + **attrs + ): + default_is_missing = attrs.get("default", _missing) is _missing + Parameter.__init__(self, param_decls, type=type, **attrs) + + if prompt is True: + prompt_text = self.name.replace("_", " ").capitalize() + elif prompt is False: + prompt_text = None + else: + prompt_text = prompt + self.prompt = prompt_text + self.confirmation_prompt = confirmation_prompt + self.hide_input = hide_input + self.hidden = hidden + + # Flags + if is_flag is None: + if flag_value is not None: + is_flag = True + else: + is_flag = bool(self.secondary_opts) + if is_flag and default_is_missing: + self.default = False + if flag_value is None: + flag_value = not self.default + self.is_flag = is_flag + self.flag_value = flag_value + if self.is_flag and isinstance(self.flag_value, bool) and type in [None, bool]: + self.type = BOOL + self.is_bool_flag = True + else: + self.is_bool_flag = False + + # Counting + self.count = count + if count: + if type is None: + self.type = IntRange(min=0) + if default_is_missing: + self.default = 0 + + self.multiple = multiple + self.allow_from_autoenv = allow_from_autoenv + self.help = help + self.show_default = show_default + self.show_choices = show_choices + self.show_envvar = show_envvar + + # Sanity check for stuff we don't support + if __debug__: + if self.nargs < 0: + raise TypeError("Options cannot have nargs < 0") + if self.prompt and self.is_flag and not self.is_bool_flag: + raise TypeError("Cannot prompt for flags that are not bools.") + if not self.is_bool_flag and self.secondary_opts: + raise TypeError("Got secondary option for non boolean flag.") + if self.is_bool_flag and self.hide_input and self.prompt is not None: + raise TypeError("Hidden input does not work with boolean flag prompts.") + if self.count: + if self.multiple: + raise TypeError( + "Options cannot be multiple and count at the same time." + ) + elif self.is_flag: + raise TypeError( + "Options cannot be count and flags at the same time." + ) + + def _parse_decls(self, decls, expose_value): + opts = [] + secondary_opts = [] + name = None + possible_names = [] + + for decl in decls: + if isidentifier(decl): + if name is not None: + raise TypeError("Name defined twice") + name = decl + else: + split_char = ";" if decl[:1] == "/" else "/" + if split_char in decl: + first, second = decl.split(split_char, 1) + first = first.rstrip() + if first: + possible_names.append(split_opt(first)) + opts.append(first) + second = second.lstrip() + if second: + secondary_opts.append(second.lstrip()) + else: + possible_names.append(split_opt(decl)) + opts.append(decl) + + if name is None and possible_names: + possible_names.sort(key=lambda x: -len(x[0])) # group long options first + name = possible_names[0][1].replace("-", "_").lower() + if not isidentifier(name): + name = None + + if name is None: + if not expose_value: + return None, opts, secondary_opts + raise TypeError("Could not determine name for option") + + if not opts and not secondary_opts: + raise TypeError( + "No options defined but a name was passed ({}). Did you" + " mean to declare an argument instead of an option?".format(name) + ) + + return name, opts, secondary_opts + + def add_to_parser(self, parser, ctx): + kwargs = { + "dest": self.name, + "nargs": self.nargs, + "obj": self, + } + + if self.multiple: + action = "append" + elif self.count: + action = "count" + else: + action = "store" + + if self.is_flag: + kwargs.pop("nargs", None) + action_const = "{}_const".format(action) + if self.is_bool_flag and self.secondary_opts: + parser.add_option(self.opts, action=action_const, const=True, **kwargs) + parser.add_option( + self.secondary_opts, action=action_const, const=False, **kwargs + ) + else: + parser.add_option( + self.opts, action=action_const, const=self.flag_value, **kwargs + ) + else: + kwargs["action"] = action + parser.add_option(self.opts, **kwargs) + + def get_help_record(self, ctx): + if self.hidden: + return + any_prefix_is_slash = [] + + def _write_opts(opts): + rv, any_slashes = join_options(opts) + if any_slashes: + any_prefix_is_slash[:] = [True] + if not self.is_flag and not self.count: + rv += " {}".format(self.make_metavar()) + return rv + + rv = [_write_opts(self.opts)] + if self.secondary_opts: + rv.append(_write_opts(self.secondary_opts)) + + help = self.help or "" + extra = [] + if self.show_envvar: + envvar = self.envvar + if envvar is None: + if self.allow_from_autoenv and ctx.auto_envvar_prefix is not None: + envvar = "{}_{}".format(ctx.auto_envvar_prefix, self.name.upper()) + if envvar is not None: + extra.append( + "env var: {}".format( + ", ".join(str(d) for d in envvar) + if isinstance(envvar, (list, tuple)) + else envvar + ) + ) + if self.default is not None and (self.show_default or ctx.show_default): + if isinstance(self.show_default, string_types): + default_string = "({})".format(self.show_default) + elif isinstance(self.default, (list, tuple)): + default_string = ", ".join(str(d) for d in self.default) + elif inspect.isfunction(self.default): + default_string = "(dynamic)" + else: + default_string = self.default + extra.append("default: {}".format(default_string)) + + if self.required: + extra.append("required") + if extra: + help = "{}[{}]".format( + "{} ".format(help) if help else "", "; ".join(extra) + ) + + return ("; " if any_prefix_is_slash else " / ").join(rv), help + + def get_default(self, ctx): + # If we're a non boolean flag our default is more complex because + # we need to look at all flags in the same group to figure out + # if we're the the default one in which case we return the flag + # value as default. + if self.is_flag and not self.is_bool_flag: + for param in ctx.command.params: + if param.name == self.name and param.default: + return param.flag_value + return None + return Parameter.get_default(self, ctx) + + def prompt_for_value(self, ctx): + """This is an alternative flow that can be activated in the full + value processing if a value does not exist. It will prompt the + user until a valid value exists and then returns the processed + value as result. + """ + # Calculate the default before prompting anything to be stable. + default = self.get_default(ctx) + + # If this is a prompt for a flag we need to handle this + # differently. + if self.is_bool_flag: + return confirm(self.prompt, default) + + return prompt( + self.prompt, + default=default, + type=self.type, + hide_input=self.hide_input, + show_choices=self.show_choices, + confirmation_prompt=self.confirmation_prompt, + value_proc=lambda x: self.process_value(ctx, x), + ) + + def resolve_envvar_value(self, ctx): + rv = Parameter.resolve_envvar_value(self, ctx) + if rv is not None: + return rv + if self.allow_from_autoenv and ctx.auto_envvar_prefix is not None: + envvar = "{}_{}".format(ctx.auto_envvar_prefix, self.name.upper()) + return os.environ.get(envvar) + + def value_from_envvar(self, ctx): + rv = self.resolve_envvar_value(ctx) + if rv is None: + return None + value_depth = (self.nargs != 1) + bool(self.multiple) + if value_depth > 0 and rv is not None: + rv = self.type.split_envvar_value(rv) + if self.multiple and self.nargs != 1: + rv = batch(rv, self.nargs) + return rv + + def full_process_value(self, ctx, value): + if value is None and self.prompt is not None and not ctx.resilient_parsing: + return self.prompt_for_value(ctx) + return Parameter.full_process_value(self, ctx, value) + + +class Argument(Parameter): + """Arguments are positional parameters to a command. They generally + provide fewer features than options but can have infinite ``nargs`` + and are required by default. + + All parameters are passed onwards to the parameter constructor. + """ + + param_type_name = "argument" + + def __init__(self, param_decls, required=None, **attrs): + if required is None: + if attrs.get("default") is not None: + required = False + else: + required = attrs.get("nargs", 1) > 0 + Parameter.__init__(self, param_decls, required=required, **attrs) + if self.default is not None and self.nargs < 0: + raise TypeError( + "nargs=-1 in combination with a default value is not supported." + ) + + @property + def human_readable_name(self): + if self.metavar is not None: + return self.metavar + return self.name.upper() + + def make_metavar(self): + if self.metavar is not None: + return self.metavar + var = self.type.get_metavar(self) + if not var: + var = self.name.upper() + if not self.required: + var = "[{}]".format(var) + if self.nargs != 1: + var += "..." + return var + + def _parse_decls(self, decls, expose_value): + if not decls: + if not expose_value: + return None, [], [] + raise TypeError("Could not determine name for argument") + if len(decls) == 1: + name = arg = decls[0] + name = name.replace("-", "_").lower() + else: + raise TypeError( + "Arguments take exactly one parameter declaration, got" + " {}".format(len(decls)) + ) + return name, [arg], [] + + def get_usage_pieces(self, ctx): + return [self.make_metavar()] + + def get_error_hint(self, ctx): + return repr(self.make_metavar()) + + def add_to_parser(self, parser, ctx): + parser.add_argument(dest=self.name, nargs=self.nargs, obj=self) diff --git a/robot/lib/python3.8/site-packages/click/decorators.py b/robot/lib/python3.8/site-packages/click/decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..c7b5af6cc57fd7e120a1537fa4bf3906b16a24dd --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/decorators.py @@ -0,0 +1,333 @@ +import inspect +import sys +from functools import update_wrapper + +from ._compat import iteritems +from ._unicodefun import _check_for_unicode_literals +from .core import Argument +from .core import Command +from .core import Group +from .core import Option +from .globals import get_current_context +from .utils import echo + + +def pass_context(f): + """Marks a callback as wanting to receive the current context + object as first argument. + """ + + def new_func(*args, **kwargs): + return f(get_current_context(), *args, **kwargs) + + return update_wrapper(new_func, f) + + +def pass_obj(f): + """Similar to :func:`pass_context`, but only pass the object on the + context onwards (:attr:`Context.obj`). This is useful if that object + represents the state of a nested system. + """ + + def new_func(*args, **kwargs): + return f(get_current_context().obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + +def make_pass_decorator(object_type, ensure=False): + """Given an object type this creates a decorator that will work + similar to :func:`pass_obj` but instead of passing the object of the + current context, it will find the innermost context of type + :func:`object_type`. + + This generates a decorator that works roughly like this:: + + from functools import update_wrapper + + def decorator(f): + @pass_context + def new_func(ctx, *args, **kwargs): + obj = ctx.find_object(object_type) + return ctx.invoke(f, obj, *args, **kwargs) + return update_wrapper(new_func, f) + return decorator + + :param object_type: the type of the object to pass. + :param ensure: if set to `True`, a new object will be created and + remembered on the context if it's not there yet. + """ + + def decorator(f): + def new_func(*args, **kwargs): + ctx = get_current_context() + if ensure: + obj = ctx.ensure_object(object_type) + else: + obj = ctx.find_object(object_type) + if obj is None: + raise RuntimeError( + "Managed to invoke callback without a context" + " object of type '{}' existing".format(object_type.__name__) + ) + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + return decorator + + +def _make_command(f, name, attrs, cls): + if isinstance(f, Command): + raise TypeError("Attempted to convert a callback into a command twice.") + try: + params = f.__click_params__ + params.reverse() + del f.__click_params__ + except AttributeError: + params = [] + help = attrs.get("help") + if help is None: + help = inspect.getdoc(f) + if isinstance(help, bytes): + help = help.decode("utf-8") + else: + help = inspect.cleandoc(help) + attrs["help"] = help + _check_for_unicode_literals() + return cls( + name=name or f.__name__.lower().replace("_", "-"), + callback=f, + params=params, + **attrs + ) + + +def command(name=None, cls=None, **attrs): + r"""Creates a new :class:`Command` and uses the decorated function as + callback. This will also automatically attach all decorated + :func:`option`\s and :func:`argument`\s as parameters to the command. + + The name of the command defaults to the name of the function with + underscores replaced by dashes. If you want to change that, you can + pass the intended name as the first argument. + + All keyword arguments are forwarded to the underlying command class. + + Once decorated the function turns into a :class:`Command` instance + that can be invoked as a command line utility or be attached to a + command :class:`Group`. + + :param name: the name of the command. This defaults to the function + name with underscores replaced by dashes. + :param cls: the command class to instantiate. This defaults to + :class:`Command`. + """ + if cls is None: + cls = Command + + def decorator(f): + cmd = _make_command(f, name, attrs, cls) + cmd.__doc__ = f.__doc__ + return cmd + + return decorator + + +def group(name=None, **attrs): + """Creates a new :class:`Group` with a function as callback. This + works otherwise the same as :func:`command` just that the `cls` + parameter is set to :class:`Group`. + """ + attrs.setdefault("cls", Group) + return command(name, **attrs) + + +def _param_memo(f, param): + if isinstance(f, Command): + f.params.append(param) + else: + if not hasattr(f, "__click_params__"): + f.__click_params__ = [] + f.__click_params__.append(param) + + +def argument(*param_decls, **attrs): + """Attaches an argument to the command. All positional arguments are + passed as parameter declarations to :class:`Argument`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Argument` instance manually + and attaching it to the :attr:`Command.params` list. + + :param cls: the argument class to instantiate. This defaults to + :class:`Argument`. + """ + + def decorator(f): + ArgumentClass = attrs.pop("cls", Argument) + _param_memo(f, ArgumentClass(param_decls, **attrs)) + return f + + return decorator + + +def option(*param_decls, **attrs): + """Attaches an option to the command. All positional arguments are + passed as parameter declarations to :class:`Option`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Option` instance manually + and attaching it to the :attr:`Command.params` list. + + :param cls: the option class to instantiate. This defaults to + :class:`Option`. + """ + + def decorator(f): + # Issue 926, copy attrs, so pre-defined options can re-use the same cls= + option_attrs = attrs.copy() + + if "help" in option_attrs: + option_attrs["help"] = inspect.cleandoc(option_attrs["help"]) + OptionClass = option_attrs.pop("cls", Option) + _param_memo(f, OptionClass(param_decls, **option_attrs)) + return f + + return decorator + + +def confirmation_option(*param_decls, **attrs): + """Shortcut for confirmation prompts that can be ignored by passing + ``--yes`` as parameter. + + This is equivalent to decorating a function with :func:`option` with + the following parameters:: + + def callback(ctx, param, value): + if not value: + ctx.abort() + + @click.command() + @click.option('--yes', is_flag=True, callback=callback, + expose_value=False, prompt='Do you want to continue?') + def dropdb(): + pass + """ + + def decorator(f): + def callback(ctx, param, value): + if not value: + ctx.abort() + + attrs.setdefault("is_flag", True) + attrs.setdefault("callback", callback) + attrs.setdefault("expose_value", False) + attrs.setdefault("prompt", "Do you want to continue?") + attrs.setdefault("help", "Confirm the action without prompting.") + return option(*(param_decls or ("--yes",)), **attrs)(f) + + return decorator + + +def password_option(*param_decls, **attrs): + """Shortcut for password prompts. + + This is equivalent to decorating a function with :func:`option` with + the following parameters:: + + @click.command() + @click.option('--password', prompt=True, confirmation_prompt=True, + hide_input=True) + def changeadmin(password): + pass + """ + + def decorator(f): + attrs.setdefault("prompt", True) + attrs.setdefault("confirmation_prompt", True) + attrs.setdefault("hide_input", True) + return option(*(param_decls or ("--password",)), **attrs)(f) + + return decorator + + +def version_option(version=None, *param_decls, **attrs): + """Adds a ``--version`` option which immediately ends the program + printing out the version number. This is implemented as an eager + option that prints the version and exits the program in the callback. + + :param version: the version number to show. If not provided Click + attempts an auto discovery via setuptools. + :param prog_name: the name of the program (defaults to autodetection) + :param message: custom message to show instead of the default + (``'%(prog)s, version %(version)s'``) + :param others: everything else is forwarded to :func:`option`. + """ + if version is None: + if hasattr(sys, "_getframe"): + module = sys._getframe(1).f_globals.get("__name__") + else: + module = "" + + def decorator(f): + prog_name = attrs.pop("prog_name", None) + message = attrs.pop("message", "%(prog)s, version %(version)s") + + def callback(ctx, param, value): + if not value or ctx.resilient_parsing: + return + prog = prog_name + if prog is None: + prog = ctx.find_root().info_name + ver = version + if ver is None: + try: + import pkg_resources + except ImportError: + pass + else: + for dist in pkg_resources.working_set: + scripts = dist.get_entry_map().get("console_scripts") or {} + for _, entry_point in iteritems(scripts): + if entry_point.module_name == module: + ver = dist.version + break + if ver is None: + raise RuntimeError("Could not determine version") + echo(message % {"prog": prog, "version": ver}, color=ctx.color) + ctx.exit() + + attrs.setdefault("is_flag", True) + attrs.setdefault("expose_value", False) + attrs.setdefault("is_eager", True) + attrs.setdefault("help", "Show the version and exit.") + attrs["callback"] = callback + return option(*(param_decls or ("--version",)), **attrs)(f) + + return decorator + + +def help_option(*param_decls, **attrs): + """Adds a ``--help`` option which immediately ends the program + printing out the help page. This is usually unnecessary to add as + this is added by default to all commands unless suppressed. + + Like :func:`version_option`, this is implemented as eager option that + prints in the callback and exits. + + All arguments are forwarded to :func:`option`. + """ + + def decorator(f): + def callback(ctx, param, value): + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + attrs.setdefault("is_flag", True) + attrs.setdefault("expose_value", False) + attrs.setdefault("help", "Show this message and exit.") + attrs.setdefault("is_eager", True) + attrs["callback"] = callback + return option(*(param_decls or ("--help",)), **attrs)(f) + + return decorator diff --git a/robot/lib/python3.8/site-packages/click/exceptions.py b/robot/lib/python3.8/site-packages/click/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..592ee38f0dec509dceadded39aa7f1684d853ff4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/exceptions.py @@ -0,0 +1,253 @@ +from ._compat import filename_to_ui +from ._compat import get_text_stderr +from ._compat import PY2 +from .utils import echo + + +def _join_param_hints(param_hint): + if isinstance(param_hint, (tuple, list)): + return " / ".join(repr(x) for x in param_hint) + return param_hint + + +class ClickException(Exception): + """An exception that Click can handle and show to the user.""" + + #: The exit code for this exception + exit_code = 1 + + def __init__(self, message): + ctor_msg = message + if PY2: + if ctor_msg is not None: + ctor_msg = ctor_msg.encode("utf-8") + Exception.__init__(self, ctor_msg) + self.message = message + + def format_message(self): + return self.message + + def __str__(self): + return self.message + + if PY2: + __unicode__ = __str__ + + def __str__(self): + return self.message.encode("utf-8") + + def show(self, file=None): + if file is None: + file = get_text_stderr() + echo("Error: {}".format(self.format_message()), file=file) + + +class UsageError(ClickException): + """An internal exception that signals a usage error. This typically + aborts any further handling. + + :param message: the error message to display. + :param ctx: optionally the context that caused this error. Click will + fill in the context automatically in some situations. + """ + + exit_code = 2 + + def __init__(self, message, ctx=None): + ClickException.__init__(self, message) + self.ctx = ctx + self.cmd = self.ctx.command if self.ctx else None + + def show(self, file=None): + if file is None: + file = get_text_stderr() + color = None + hint = "" + if self.cmd is not None and self.cmd.get_help_option(self.ctx) is not None: + hint = "Try '{} {}' for help.\n".format( + self.ctx.command_path, self.ctx.help_option_names[0] + ) + if self.ctx is not None: + color = self.ctx.color + echo("{}\n{}".format(self.ctx.get_usage(), hint), file=file, color=color) + echo("Error: {}".format(self.format_message()), file=file, color=color) + + +class BadParameter(UsageError): + """An exception that formats out a standardized error message for a + bad parameter. This is useful when thrown from a callback or type as + Click will attach contextual information to it (for instance, which + parameter it is). + + .. versionadded:: 2.0 + + :param param: the parameter object that caused this error. This can + be left out, and Click will attach this info itself + if possible. + :param param_hint: a string that shows up as parameter name. This + can be used as alternative to `param` in cases + where custom validation should happen. If it is + a string it's used as such, if it's a list then + each item is quoted and separated. + """ + + def __init__(self, message, ctx=None, param=None, param_hint=None): + UsageError.__init__(self, message, ctx) + self.param = param + self.param_hint = param_hint + + def format_message(self): + if self.param_hint is not None: + param_hint = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) + else: + return "Invalid value: {}".format(self.message) + param_hint = _join_param_hints(param_hint) + + return "Invalid value for {}: {}".format(param_hint, self.message) + + +class MissingParameter(BadParameter): + """Raised if click required an option or argument but it was not + provided when invoking the script. + + .. versionadded:: 4.0 + + :param param_type: a string that indicates the type of the parameter. + The default is to inherit the parameter type from + the given `param`. Valid values are ``'parameter'``, + ``'option'`` or ``'argument'``. + """ + + def __init__( + self, message=None, ctx=None, param=None, param_hint=None, param_type=None + ): + BadParameter.__init__(self, message, ctx, param, param_hint) + self.param_type = param_type + + def format_message(self): + if self.param_hint is not None: + param_hint = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) + else: + param_hint = None + param_hint = _join_param_hints(param_hint) + + param_type = self.param_type + if param_type is None and self.param is not None: + param_type = self.param.param_type_name + + msg = self.message + if self.param is not None: + msg_extra = self.param.type.get_missing_message(self.param) + if msg_extra: + if msg: + msg += ". {}".format(msg_extra) + else: + msg = msg_extra + + return "Missing {}{}{}{}".format( + param_type, + " {}".format(param_hint) if param_hint else "", + ". " if msg else ".", + msg or "", + ) + + def __str__(self): + if self.message is None: + param_name = self.param.name if self.param else None + return "missing parameter: {}".format(param_name) + else: + return self.message + + if PY2: + __unicode__ = __str__ + + def __str__(self): + return self.__unicode__().encode("utf-8") + + +class NoSuchOption(UsageError): + """Raised if click attempted to handle an option that does not + exist. + + .. versionadded:: 4.0 + """ + + def __init__(self, option_name, message=None, possibilities=None, ctx=None): + if message is None: + message = "no such option: {}".format(option_name) + UsageError.__init__(self, message, ctx) + self.option_name = option_name + self.possibilities = possibilities + + def format_message(self): + bits = [self.message] + if self.possibilities: + if len(self.possibilities) == 1: + bits.append("Did you mean {}?".format(self.possibilities[0])) + else: + possibilities = sorted(self.possibilities) + bits.append("(Possible options: {})".format(", ".join(possibilities))) + return " ".join(bits) + + +class BadOptionUsage(UsageError): + """Raised if an option is generally supplied but the use of the option + was incorrect. This is for instance raised if the number of arguments + for an option is not correct. + + .. versionadded:: 4.0 + + :param option_name: the name of the option being used incorrectly. + """ + + def __init__(self, option_name, message, ctx=None): + UsageError.__init__(self, message, ctx) + self.option_name = option_name + + +class BadArgumentUsage(UsageError): + """Raised if an argument is generally supplied but the use of the argument + was incorrect. This is for instance raised if the number of values + for an argument is not correct. + + .. versionadded:: 6.0 + """ + + def __init__(self, message, ctx=None): + UsageError.__init__(self, message, ctx) + + +class FileError(ClickException): + """Raised if a file cannot be opened.""" + + def __init__(self, filename, hint=None): + ui_filename = filename_to_ui(filename) + if hint is None: + hint = "unknown error" + ClickException.__init__(self, hint) + self.ui_filename = ui_filename + self.filename = filename + + def format_message(self): + return "Could not open file {}: {}".format(self.ui_filename, self.message) + + +class Abort(RuntimeError): + """An internal signalling exception that signals Click to abort.""" + + +class Exit(RuntimeError): + """An exception that indicates that the application should exit with some + status code. + + :param code: the status code to exit with. + """ + + __slots__ = ("exit_code",) + + def __init__(self, code=0): + self.exit_code = code diff --git a/robot/lib/python3.8/site-packages/click/formatting.py b/robot/lib/python3.8/site-packages/click/formatting.py new file mode 100644 index 0000000000000000000000000000000000000000..319c7f6163e266de1abe6f96bd4290193184ec6c --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/formatting.py @@ -0,0 +1,283 @@ +from contextlib import contextmanager + +from ._compat import term_len +from .parser import split_opt +from .termui import get_terminal_size + +# Can force a width. This is used by the test system +FORCED_WIDTH = None + + +def measure_table(rows): + widths = {} + for row in rows: + for idx, col in enumerate(row): + widths[idx] = max(widths.get(idx, 0), term_len(col)) + return tuple(y for x, y in sorted(widths.items())) + + +def iter_rows(rows, col_count): + for row in rows: + row = tuple(row) + yield row + ("",) * (col_count - len(row)) + + +def wrap_text( + text, width=78, initial_indent="", subsequent_indent="", preserve_paragraphs=False +): + """A helper function that intelligently wraps text. By default, it + assumes that it operates on a single paragraph of text but if the + `preserve_paragraphs` parameter is provided it will intelligently + handle paragraphs (defined by two empty lines). + + If paragraphs are handled, a paragraph can be prefixed with an empty + line containing the ``\\b`` character (``\\x08``) to indicate that + no rewrapping should happen in that block. + + :param text: the text that should be rewrapped. + :param width: the maximum width for the text. + :param initial_indent: the initial indent that should be placed on the + first line as a string. + :param subsequent_indent: the indent string that should be placed on + each consecutive line. + :param preserve_paragraphs: if this flag is set then the wrapping will + intelligently handle paragraphs. + """ + from ._textwrap import TextWrapper + + text = text.expandtabs() + wrapper = TextWrapper( + width, + initial_indent=initial_indent, + subsequent_indent=subsequent_indent, + replace_whitespace=False, + ) + if not preserve_paragraphs: + return wrapper.fill(text) + + p = [] + buf = [] + indent = None + + def _flush_par(): + if not buf: + return + if buf[0].strip() == "\b": + p.append((indent or 0, True, "\n".join(buf[1:]))) + else: + p.append((indent or 0, False, " ".join(buf))) + del buf[:] + + for line in text.splitlines(): + if not line: + _flush_par() + indent = None + else: + if indent is None: + orig_len = term_len(line) + line = line.lstrip() + indent = orig_len - term_len(line) + buf.append(line) + _flush_par() + + rv = [] + for indent, raw, text in p: + with wrapper.extra_indent(" " * indent): + if raw: + rv.append(wrapper.indent_only(text)) + else: + rv.append(wrapper.fill(text)) + + return "\n\n".join(rv) + + +class HelpFormatter(object): + """This class helps with formatting text-based help pages. It's + usually just needed for very special internal cases, but it's also + exposed so that developers can write their own fancy outputs. + + At present, it always writes into memory. + + :param indent_increment: the additional increment for each level. + :param width: the width for the text. This defaults to the terminal + width clamped to a maximum of 78. + """ + + def __init__(self, indent_increment=2, width=None, max_width=None): + self.indent_increment = indent_increment + if max_width is None: + max_width = 80 + if width is None: + width = FORCED_WIDTH + if width is None: + width = max(min(get_terminal_size()[0], max_width) - 2, 50) + self.width = width + self.current_indent = 0 + self.buffer = [] + + def write(self, string): + """Writes a unicode string into the internal buffer.""" + self.buffer.append(string) + + def indent(self): + """Increases the indentation.""" + self.current_indent += self.indent_increment + + def dedent(self): + """Decreases the indentation.""" + self.current_indent -= self.indent_increment + + def write_usage(self, prog, args="", prefix="Usage: "): + """Writes a usage line into the buffer. + + :param prog: the program name. + :param args: whitespace separated list of arguments. + :param prefix: the prefix for the first line. + """ + usage_prefix = "{:>{w}}{} ".format(prefix, prog, w=self.current_indent) + text_width = self.width - self.current_indent + + if text_width >= (term_len(usage_prefix) + 20): + # The arguments will fit to the right of the prefix. + indent = " " * term_len(usage_prefix) + self.write( + wrap_text( + args, + text_width, + initial_indent=usage_prefix, + subsequent_indent=indent, + ) + ) + else: + # The prefix is too long, put the arguments on the next line. + self.write(usage_prefix) + self.write("\n") + indent = " " * (max(self.current_indent, term_len(prefix)) + 4) + self.write( + wrap_text( + args, text_width, initial_indent=indent, subsequent_indent=indent + ) + ) + + self.write("\n") + + def write_heading(self, heading): + """Writes a heading into the buffer.""" + self.write("{:>{w}}{}:\n".format("", heading, w=self.current_indent)) + + def write_paragraph(self): + """Writes a paragraph into the buffer.""" + if self.buffer: + self.write("\n") + + def write_text(self, text): + """Writes re-indented text into the buffer. This rewraps and + preserves paragraphs. + """ + text_width = max(self.width - self.current_indent, 11) + indent = " " * self.current_indent + self.write( + wrap_text( + text, + text_width, + initial_indent=indent, + subsequent_indent=indent, + preserve_paragraphs=True, + ) + ) + self.write("\n") + + def write_dl(self, rows, col_max=30, col_spacing=2): + """Writes a definition list into the buffer. This is how options + and commands are usually formatted. + + :param rows: a list of two item tuples for the terms and values. + :param col_max: the maximum width of the first column. + :param col_spacing: the number of spaces between the first and + second column. + """ + rows = list(rows) + widths = measure_table(rows) + if len(widths) != 2: + raise TypeError("Expected two columns for definition list") + + first_col = min(widths[0], col_max) + col_spacing + + for first, second in iter_rows(rows, len(widths)): + self.write("{:>{w}}{}".format("", first, w=self.current_indent)) + if not second: + self.write("\n") + continue + if term_len(first) <= first_col - col_spacing: + self.write(" " * (first_col - term_len(first))) + else: + self.write("\n") + self.write(" " * (first_col + self.current_indent)) + + text_width = max(self.width - first_col - 2, 10) + wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True) + lines = wrapped_text.splitlines() + + if lines: + self.write("{}\n".format(lines[0])) + + for line in lines[1:]: + self.write( + "{:>{w}}{}\n".format( + "", line, w=first_col + self.current_indent + ) + ) + + if len(lines) > 1: + # separate long help from next option + self.write("\n") + else: + self.write("\n") + + @contextmanager + def section(self, name): + """Helpful context manager that writes a paragraph, a heading, + and the indents. + + :param name: the section name that is written as heading. + """ + self.write_paragraph() + self.write_heading(name) + self.indent() + try: + yield + finally: + self.dedent() + + @contextmanager + def indentation(self): + """A context manager that increases the indentation.""" + self.indent() + try: + yield + finally: + self.dedent() + + def getvalue(self): + """Returns the buffer contents.""" + return "".join(self.buffer) + + +def join_options(options): + """Given a list of option strings this joins them in the most appropriate + way and returns them in the form ``(formatted_string, + any_prefix_is_slash)`` where the second item in the tuple is a flag that + indicates if any of the option prefixes was a slash. + """ + rv = [] + any_prefix_is_slash = False + for opt in options: + prefix = split_opt(opt)[0] + if prefix == "/": + any_prefix_is_slash = True + rv.append((len(prefix), opt)) + + rv.sort(key=lambda x: x[0]) + + rv = ", ".join(x[1] for x in rv) + return rv, any_prefix_is_slash diff --git a/robot/lib/python3.8/site-packages/click/globals.py b/robot/lib/python3.8/site-packages/click/globals.py new file mode 100644 index 0000000000000000000000000000000000000000..1649f9a0bfbe60ba6c386d18634cdd77eb8df600 --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/globals.py @@ -0,0 +1,47 @@ +from threading import local + +_local = local() + + +def get_current_context(silent=False): + """Returns the current click context. This can be used as a way to + access the current context object from anywhere. This is a more implicit + alternative to the :func:`pass_context` decorator. This function is + primarily useful for helpers such as :func:`echo` which might be + interested in changing its behavior based on the current context. + + To push the current context, :meth:`Context.scope` can be used. + + .. versionadded:: 5.0 + + :param silent: if set to `True` the return value is `None` if no context + is available. The default behavior is to raise a + :exc:`RuntimeError`. + """ + try: + return _local.stack[-1] + except (AttributeError, IndexError): + if not silent: + raise RuntimeError("There is no active click context.") + + +def push_context(ctx): + """Pushes a new context to the current stack.""" + _local.__dict__.setdefault("stack", []).append(ctx) + + +def pop_context(): + """Removes the top level from the stack.""" + _local.stack.pop() + + +def resolve_color_default(color=None): + """"Internal helper to get the default value of the color flag. If a + value is passed it's returned unchanged, otherwise it's looked up from + the current context. + """ + if color is not None: + return color + ctx = get_current_context(silent=True) + if ctx is not None: + return ctx.color diff --git a/robot/lib/python3.8/site-packages/click/parser.py b/robot/lib/python3.8/site-packages/click/parser.py new file mode 100644 index 0000000000000000000000000000000000000000..f43ebfe9fc095d168da5a5cc7d024cad7d6c274f --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/parser.py @@ -0,0 +1,428 @@ +# -*- coding: utf-8 -*- +""" +This module started out as largely a copy paste from the stdlib's +optparse module with the features removed that we do not need from +optparse because we implement them in Click on a higher level (for +instance type handling, help formatting and a lot more). + +The plan is to remove more and more from here over time. + +The reason this is a different module and not optparse from the stdlib +is that there are differences in 2.x and 3.x about the error messages +generated and optparse in the stdlib uses gettext for no good reason +and might cause us issues. + +Click uses parts of optparse written by Gregory P. Ward and maintained +by the Python Software Foundation. This is limited to code in parser.py. + +Copyright 2001-2006 Gregory P. Ward. All rights reserved. +Copyright 2002-2006 Python Software Foundation. All rights reserved. +""" +import re +from collections import deque + +from .exceptions import BadArgumentUsage +from .exceptions import BadOptionUsage +from .exceptions import NoSuchOption +from .exceptions import UsageError + + +def _unpack_args(args, nargs_spec): + """Given an iterable of arguments and an iterable of nargs specifications, + it returns a tuple with all the unpacked arguments at the first index + and all remaining arguments as the second. + + The nargs specification is the number of arguments that should be consumed + or `-1` to indicate that this position should eat up all the remainders. + + Missing items are filled with `None`. + """ + args = deque(args) + nargs_spec = deque(nargs_spec) + rv = [] + spos = None + + def _fetch(c): + try: + if spos is None: + return c.popleft() + else: + return c.pop() + except IndexError: + return None + + while nargs_spec: + nargs = _fetch(nargs_spec) + if nargs == 1: + rv.append(_fetch(args)) + elif nargs > 1: + x = [_fetch(args) for _ in range(nargs)] + # If we're reversed, we're pulling in the arguments in reverse, + # so we need to turn them around. + if spos is not None: + x.reverse() + rv.append(tuple(x)) + elif nargs < 0: + if spos is not None: + raise TypeError("Cannot have two nargs < 0") + spos = len(rv) + rv.append(None) + + # spos is the position of the wildcard (star). If it's not `None`, + # we fill it with the remainder. + if spos is not None: + rv[spos] = tuple(args) + args = [] + rv[spos + 1 :] = reversed(rv[spos + 1 :]) + + return tuple(rv), list(args) + + +def _error_opt_args(nargs, opt): + if nargs == 1: + raise BadOptionUsage(opt, "{} option requires an argument".format(opt)) + raise BadOptionUsage(opt, "{} option requires {} arguments".format(opt, nargs)) + + +def split_opt(opt): + first = opt[:1] + if first.isalnum(): + return "", opt + if opt[1:2] == first: + return opt[:2], opt[2:] + return first, opt[1:] + + +def normalize_opt(opt, ctx): + if ctx is None or ctx.token_normalize_func is None: + return opt + prefix, opt = split_opt(opt) + return prefix + ctx.token_normalize_func(opt) + + +def split_arg_string(string): + """Given an argument string this attempts to split it into small parts.""" + rv = [] + for match in re.finditer( + r"('([^'\\]*(?:\\.[^'\\]*)*)'|\"([^\"\\]*(?:\\.[^\"\\]*)*)\"|\S+)\s*", + string, + re.S, + ): + arg = match.group().strip() + if arg[:1] == arg[-1:] and arg[:1] in "\"'": + arg = arg[1:-1].encode("ascii", "backslashreplace").decode("unicode-escape") + try: + arg = type(string)(arg) + except UnicodeError: + pass + rv.append(arg) + return rv + + +class Option(object): + def __init__(self, opts, dest, action=None, nargs=1, const=None, obj=None): + self._short_opts = [] + self._long_opts = [] + self.prefixes = set() + + for opt in opts: + prefix, value = split_opt(opt) + if not prefix: + raise ValueError("Invalid start character for option ({})".format(opt)) + self.prefixes.add(prefix[0]) + if len(prefix) == 1 and len(value) == 1: + self._short_opts.append(opt) + else: + self._long_opts.append(opt) + self.prefixes.add(prefix) + + if action is None: + action = "store" + + self.dest = dest + self.action = action + self.nargs = nargs + self.const = const + self.obj = obj + + @property + def takes_value(self): + return self.action in ("store", "append") + + def process(self, value, state): + if self.action == "store": + state.opts[self.dest] = value + elif self.action == "store_const": + state.opts[self.dest] = self.const + elif self.action == "append": + state.opts.setdefault(self.dest, []).append(value) + elif self.action == "append_const": + state.opts.setdefault(self.dest, []).append(self.const) + elif self.action == "count": + state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 + else: + raise ValueError("unknown action '{}'".format(self.action)) + state.order.append(self.obj) + + +class Argument(object): + def __init__(self, dest, nargs=1, obj=None): + self.dest = dest + self.nargs = nargs + self.obj = obj + + def process(self, value, state): + if self.nargs > 1: + holes = sum(1 for x in value if x is None) + if holes == len(value): + value = None + elif holes != 0: + raise BadArgumentUsage( + "argument {} takes {} values".format(self.dest, self.nargs) + ) + state.opts[self.dest] = value + state.order.append(self.obj) + + +class ParsingState(object): + def __init__(self, rargs): + self.opts = {} + self.largs = [] + self.rargs = rargs + self.order = [] + + +class OptionParser(object): + """The option parser is an internal class that is ultimately used to + parse options and arguments. It's modelled after optparse and brings + a similar but vastly simplified API. It should generally not be used + directly as the high level Click classes wrap it for you. + + It's not nearly as extensible as optparse or argparse as it does not + implement features that are implemented on a higher level (such as + types or defaults). + + :param ctx: optionally the :class:`~click.Context` where this parser + should go with. + """ + + def __init__(self, ctx=None): + #: The :class:`~click.Context` for this parser. This might be + #: `None` for some advanced use cases. + self.ctx = ctx + #: This controls how the parser deals with interspersed arguments. + #: If this is set to `False`, the parser will stop on the first + #: non-option. Click uses this to implement nested subcommands + #: safely. + self.allow_interspersed_args = True + #: This tells the parser how to deal with unknown options. By + #: default it will error out (which is sensible), but there is a + #: second mode where it will ignore it and continue processing + #: after shifting all the unknown options into the resulting args. + self.ignore_unknown_options = False + if ctx is not None: + self.allow_interspersed_args = ctx.allow_interspersed_args + self.ignore_unknown_options = ctx.ignore_unknown_options + self._short_opt = {} + self._long_opt = {} + self._opt_prefixes = {"-", "--"} + self._args = [] + + def add_option(self, opts, dest, action=None, nargs=1, const=None, obj=None): + """Adds a new option named `dest` to the parser. The destination + is not inferred (unlike with optparse) and needs to be explicitly + provided. Action can be any of ``store``, ``store_const``, + ``append``, ``appnd_const`` or ``count``. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + if obj is None: + obj = dest + opts = [normalize_opt(opt, self.ctx) for opt in opts] + option = Option(opts, dest, action=action, nargs=nargs, const=const, obj=obj) + self._opt_prefixes.update(option.prefixes) + for opt in option._short_opts: + self._short_opt[opt] = option + for opt in option._long_opts: + self._long_opt[opt] = option + + def add_argument(self, dest, nargs=1, obj=None): + """Adds a positional argument named `dest` to the parser. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + if obj is None: + obj = dest + self._args.append(Argument(dest=dest, nargs=nargs, obj=obj)) + + def parse_args(self, args): + """Parses positional arguments and returns ``(values, args, order)`` + for the parsed options and arguments as well as the leftover + arguments if there are any. The order is a list of objects as they + appear on the command line. If arguments appear multiple times they + will be memorized multiple times as well. + """ + state = ParsingState(args) + try: + self._process_args_for_options(state) + self._process_args_for_args(state) + except UsageError: + if self.ctx is None or not self.ctx.resilient_parsing: + raise + return state.opts, state.largs, state.order + + def _process_args_for_args(self, state): + pargs, args = _unpack_args( + state.largs + state.rargs, [x.nargs for x in self._args] + ) + + for idx, arg in enumerate(self._args): + arg.process(pargs[idx], state) + + state.largs = args + state.rargs = [] + + def _process_args_for_options(self, state): + while state.rargs: + arg = state.rargs.pop(0) + arglen = len(arg) + # Double dashes always handled explicitly regardless of what + # prefixes are valid. + if arg == "--": + return + elif arg[:1] in self._opt_prefixes and arglen > 1: + self._process_opts(arg, state) + elif self.allow_interspersed_args: + state.largs.append(arg) + else: + state.rargs.insert(0, arg) + return + + # Say this is the original argument list: + # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] + # ^ + # (we are about to process arg(i)). + # + # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of + # [arg0, ..., arg(i-1)] (any options and their arguments will have + # been removed from largs). + # + # The while loop will usually consume 1 or more arguments per pass. + # If it consumes 1 (eg. arg is an option that takes no arguments), + # then after _process_arg() is done the situation is: + # + # largs = subset of [arg0, ..., arg(i)] + # rargs = [arg(i+1), ..., arg(N-1)] + # + # If allow_interspersed_args is false, largs will always be + # *empty* -- still a subset of [arg0, ..., arg(i-1)], but + # not a very interesting subset! + + def _match_long_opt(self, opt, explicit_value, state): + if opt not in self._long_opt: + possibilities = [word for word in self._long_opt if word.startswith(opt)] + raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) + + option = self._long_opt[opt] + if option.takes_value: + # At this point it's safe to modify rargs by injecting the + # explicit value, because no exception is raised in this + # branch. This means that the inserted value will be fully + # consumed. + if explicit_value is not None: + state.rargs.insert(0, explicit_value) + + nargs = option.nargs + if len(state.rargs) < nargs: + _error_opt_args(nargs, opt) + elif nargs == 1: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + elif explicit_value is not None: + raise BadOptionUsage(opt, "{} option does not take a value".format(opt)) + + else: + value = None + + option.process(value, state) + + def _match_short_opt(self, arg, state): + stop = False + i = 1 + prefix = arg[0] + unknown_options = [] + + for ch in arg[1:]: + opt = normalize_opt(prefix + ch, self.ctx) + option = self._short_opt.get(opt) + i += 1 + + if not option: + if self.ignore_unknown_options: + unknown_options.append(ch) + continue + raise NoSuchOption(opt, ctx=self.ctx) + if option.takes_value: + # Any characters left in arg? Pretend they're the + # next arg, and stop consuming characters of arg. + if i < len(arg): + state.rargs.insert(0, arg[i:]) + stop = True + + nargs = option.nargs + if len(state.rargs) < nargs: + _error_opt_args(nargs, opt) + elif nargs == 1: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + else: + value = None + + option.process(value, state) + + if stop: + break + + # If we got any unknown options we re-combinate the string of the + # remaining options and re-attach the prefix, then report that + # to the state as new larg. This way there is basic combinatorics + # that can be achieved while still ignoring unknown arguments. + if self.ignore_unknown_options and unknown_options: + state.largs.append("{}{}".format(prefix, "".join(unknown_options))) + + def _process_opts(self, arg, state): + explicit_value = None + # Long option handling happens in two parts. The first part is + # supporting explicitly attached values. In any case, we will try + # to long match the option first. + if "=" in arg: + long_opt, explicit_value = arg.split("=", 1) + else: + long_opt = arg + norm_long_opt = normalize_opt(long_opt, self.ctx) + + # At this point we will match the (assumed) long option through + # the long option matching code. Note that this allows options + # like "-foo" to be matched as long options. + try: + self._match_long_opt(norm_long_opt, explicit_value, state) + except NoSuchOption: + # At this point the long option matching failed, and we need + # to try with short options. However there is a special rule + # which says, that if we have a two character options prefix + # (applies to "--foo" for instance), we do not dispatch to the + # short option code and will instead raise the no option + # error. + if arg[:2] not in self._opt_prefixes: + return self._match_short_opt(arg, state) + if not self.ignore_unknown_options: + raise + state.largs.append(arg) diff --git a/robot/lib/python3.8/site-packages/click/termui.py b/robot/lib/python3.8/site-packages/click/termui.py new file mode 100644 index 0000000000000000000000000000000000000000..02ef9e9f045cd437ce043a72e29cbc1de732e962 --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/termui.py @@ -0,0 +1,681 @@ +import inspect +import io +import itertools +import os +import struct +import sys + +from ._compat import DEFAULT_COLUMNS +from ._compat import get_winterm_size +from ._compat import isatty +from ._compat import raw_input +from ._compat import string_types +from ._compat import strip_ansi +from ._compat import text_type +from ._compat import WIN +from .exceptions import Abort +from .exceptions import UsageError +from .globals import resolve_color_default +from .types import Choice +from .types import convert_type +from .types import Path +from .utils import echo +from .utils import LazyFile + +# The prompt functions to use. The doc tools currently override these +# functions to customize how they work. +visible_prompt_func = raw_input + +_ansi_colors = { + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, + "reset": 39, + "bright_black": 90, + "bright_red": 91, + "bright_green": 92, + "bright_yellow": 93, + "bright_blue": 94, + "bright_magenta": 95, + "bright_cyan": 96, + "bright_white": 97, +} +_ansi_reset_all = "\033[0m" + + +def hidden_prompt_func(prompt): + import getpass + + return getpass.getpass(prompt) + + +def _build_prompt( + text, suffix, show_default=False, default=None, show_choices=True, type=None +): + prompt = text + if type is not None and show_choices and isinstance(type, Choice): + prompt += " ({})".format(", ".join(map(str, type.choices))) + if default is not None and show_default: + prompt = "{} [{}]".format(prompt, _format_default(default)) + return prompt + suffix + + +def _format_default(default): + if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): + return default.name + + return default + + +def prompt( + text, + default=None, + hide_input=False, + confirmation_prompt=False, + type=None, + value_proc=None, + prompt_suffix=": ", + show_default=True, + err=False, + show_choices=True, +): + """Prompts a user for input. This is a convenience function that can + be used to prompt a user for input later. + + If the user aborts the input by sending a interrupt signal, this + function will catch it and raise a :exc:`Abort` exception. + + .. versionadded:: 7.0 + Added the show_choices parameter. + + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param text: the text to show for the prompt. + :param default: the default value to use if no input happens. If this + is not given it will prompt until it's aborted. + :param hide_input: if this is set to true then the input value will + be hidden. + :param confirmation_prompt: asks for confirmation for the value. + :param type: the type to use to check the value against. + :param value_proc: if this parameter is provided it's a function that + is invoked instead of the type conversion to + convert a value. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + :param show_choices: Show or hide choices if the passed type is a Choice. + For example if type is a Choice of either day or week, + show_choices is true and text is "Group by" then the + prompt will be "Group by (day, week): ". + """ + result = None + + def prompt_func(text): + f = hidden_prompt_func if hide_input else visible_prompt_func + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(text, nl=False, err=err) + return f("") + except (KeyboardInterrupt, EOFError): + # getpass doesn't print a newline if the user aborts input with ^C. + # Allegedly this behavior is inherited from getpass(3). + # A doc bug has been filed at https://bugs.python.org/issue24711 + if hide_input: + echo(None, err=err) + raise Abort() + + if value_proc is None: + value_proc = convert_type(type, default) + + prompt = _build_prompt( + text, prompt_suffix, show_default, default, show_choices, type + ) + + while 1: + while 1: + value = prompt_func(prompt) + if value: + break + elif default is not None: + if isinstance(value_proc, Path): + # validate Path default value(exists, dir_okay etc.) + value = default + break + return default + try: + result = value_proc(value) + except UsageError as e: + echo("Error: {}".format(e.message), err=err) # noqa: B306 + continue + if not confirmation_prompt: + return result + while 1: + value2 = prompt_func("Repeat for confirmation: ") + if value2: + break + if value == value2: + return result + echo("Error: the two entered values do not match", err=err) + + +def confirm( + text, default=False, abort=False, prompt_suffix=": ", show_default=True, err=False +): + """Prompts for confirmation (yes/no question). + + If the user aborts the input by sending a interrupt signal this + function will catch it and raise a :exc:`Abort` exception. + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param text: the question to ask. + :param default: the default for the prompt. + :param abort: if this is set to `True` a negative answer aborts the + exception by raising :exc:`Abort`. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + prompt = _build_prompt( + text, prompt_suffix, show_default, "Y/n" if default else "y/N" + ) + while 1: + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(prompt, nl=False, err=err) + value = visible_prompt_func("").lower().strip() + except (KeyboardInterrupt, EOFError): + raise Abort() + if value in ("y", "yes"): + rv = True + elif value in ("n", "no"): + rv = False + elif value == "": + rv = default + else: + echo("Error: invalid input", err=err) + continue + break + if abort and not rv: + raise Abort() + return rv + + +def get_terminal_size(): + """Returns the current size of the terminal as tuple in the form + ``(width, height)`` in columns and rows. + """ + # If shutil has get_terminal_size() (Python 3.3 and later) use that + if sys.version_info >= (3, 3): + import shutil + + shutil_get_terminal_size = getattr(shutil, "get_terminal_size", None) + if shutil_get_terminal_size: + sz = shutil_get_terminal_size() + return sz.columns, sz.lines + + # We provide a sensible default for get_winterm_size() when being invoked + # inside a subprocess. Without this, it would not provide a useful input. + if get_winterm_size is not None: + size = get_winterm_size() + if size == (0, 0): + return (79, 24) + else: + return size + + def ioctl_gwinsz(fd): + try: + import fcntl + import termios + + cr = struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234")) + except Exception: + return + return cr + + cr = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2) + if not cr: + try: + fd = os.open(os.ctermid(), os.O_RDONLY) + try: + cr = ioctl_gwinsz(fd) + finally: + os.close(fd) + except Exception: + pass + if not cr or not cr[0] or not cr[1]: + cr = (os.environ.get("LINES", 25), os.environ.get("COLUMNS", DEFAULT_COLUMNS)) + return int(cr[1]), int(cr[0]) + + +def echo_via_pager(text_or_generator, color=None): + """This function takes a text and shows it via an environment specific + pager on stdout. + + .. versionchanged:: 3.0 + Added the `color` flag. + + :param text_or_generator: the text to page, or alternatively, a + generator emitting the text to page. + :param color: controls if the pager supports ANSI colors or not. The + default is autodetection. + """ + color = resolve_color_default(color) + + if inspect.isgeneratorfunction(text_or_generator): + i = text_or_generator() + elif isinstance(text_or_generator, string_types): + i = [text_or_generator] + else: + i = iter(text_or_generator) + + # convert every element of i to a text type if necessary + text_generator = (el if isinstance(el, string_types) else text_type(el) for el in i) + + from ._termui_impl import pager + + return pager(itertools.chain(text_generator, "\n"), color) + + +def progressbar( + iterable=None, + length=None, + label=None, + show_eta=True, + show_percent=None, + show_pos=False, + item_show_func=None, + fill_char="#", + empty_char="-", + bar_template="%(label)s [%(bar)s] %(info)s", + info_sep=" ", + width=36, + file=None, + color=None, +): + """This function creates an iterable context manager that can be used + to iterate over something while showing a progress bar. It will + either iterate over the `iterable` or `length` items (that are counted + up). While iteration happens, this function will print a rendered + progress bar to the given `file` (defaults to stdout) and will attempt + to calculate remaining time and more. By default, this progress bar + will not be rendered if the file is not a terminal. + + The context manager creates the progress bar. When the context + manager is entered the progress bar is already created. With every + iteration over the progress bar, the iterable passed to the bar is + advanced and the bar is updated. When the context manager exits, + a newline is printed and the progress bar is finalized on screen. + + Note: The progress bar is currently designed for use cases where the + total progress can be expected to take at least several seconds. + Because of this, the ProgressBar class object won't display + progress that is considered too fast, and progress where the time + between steps is less than a second. + + No printing must happen or the progress bar will be unintentionally + destroyed. + + Example usage:: + + with progressbar(items) as bar: + for item in bar: + do_something_with(item) + + Alternatively, if no iterable is specified, one can manually update the + progress bar through the `update()` method instead of directly + iterating over the progress bar. The update method accepts the number + of steps to increment the bar with:: + + with progressbar(length=chunks.total_bytes) as bar: + for chunk in chunks: + process_chunk(chunk) + bar.update(chunks.bytes) + + .. versionadded:: 2.0 + + .. versionadded:: 4.0 + Added the `color` parameter. Added a `update` method to the + progressbar object. + + :param iterable: an iterable to iterate over. If not provided the length + is required. + :param length: the number of items to iterate over. By default the + progressbar will attempt to ask the iterator about its + length, which might or might not work. If an iterable is + also provided this parameter can be used to override the + length. If an iterable is not provided the progress bar + will iterate over a range of that length. + :param label: the label to show next to the progress bar. + :param show_eta: enables or disables the estimated time display. This is + automatically disabled if the length cannot be + determined. + :param show_percent: enables or disables the percentage display. The + default is `True` if the iterable has a length or + `False` if not. + :param show_pos: enables or disables the absolute position display. The + default is `False`. + :param item_show_func: a function called with the current item which + can return a string to show the current item + next to the progress bar. Note that the current + item can be `None`! + :param fill_char: the character to use to show the filled part of the + progress bar. + :param empty_char: the character to use to show the non-filled part of + the progress bar. + :param bar_template: the format string to use as template for the bar. + The parameters in it are ``label`` for the label, + ``bar`` for the progress bar and ``info`` for the + info section. + :param info_sep: the separator between multiple info items (eta etc.) + :param width: the width of the progress bar in characters, 0 means full + terminal width + :param file: the file to write to. If this is not a terminal then + only the label is printed. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are included anywhere in the progress bar output + which is not the case by default. + """ + from ._termui_impl import ProgressBar + + color = resolve_color_default(color) + return ProgressBar( + iterable=iterable, + length=length, + show_eta=show_eta, + show_percent=show_percent, + show_pos=show_pos, + item_show_func=item_show_func, + fill_char=fill_char, + empty_char=empty_char, + bar_template=bar_template, + info_sep=info_sep, + file=file, + label=label, + width=width, + color=color, + ) + + +def clear(): + """Clears the terminal screen. This will have the effect of clearing + the whole visible space of the terminal and moving the cursor to the + top left. This does not do anything if not connected to a terminal. + + .. versionadded:: 2.0 + """ + if not isatty(sys.stdout): + return + # If we're on Windows and we don't have colorama available, then we + # clear the screen by shelling out. Otherwise we can use an escape + # sequence. + if WIN: + os.system("cls") + else: + sys.stdout.write("\033[2J\033[1;1H") + + +def style( + text, + fg=None, + bg=None, + bold=None, + dim=None, + underline=None, + blink=None, + reverse=None, + reset=True, +): + """Styles a text with ANSI styles and returns the new string. By + default the styling is self contained which means that at the end + of the string a reset code is issued. This can be prevented by + passing ``reset=False``. + + Examples:: + + click.echo(click.style('Hello World!', fg='green')) + click.echo(click.style('ATTENTION!', blink=True)) + click.echo(click.style('Some things', reverse=True, fg='cyan')) + + Supported color names: + + * ``black`` (might be a gray) + * ``red`` + * ``green`` + * ``yellow`` (might be an orange) + * ``blue`` + * ``magenta`` + * ``cyan`` + * ``white`` (might be light gray) + * ``bright_black`` + * ``bright_red`` + * ``bright_green`` + * ``bright_yellow`` + * ``bright_blue`` + * ``bright_magenta`` + * ``bright_cyan`` + * ``bright_white`` + * ``reset`` (reset the color code only) + + .. versionadded:: 2.0 + + .. versionadded:: 7.0 + Added support for bright colors. + + :param text: the string to style with ansi codes. + :param fg: if provided this will become the foreground color. + :param bg: if provided this will become the background color. + :param bold: if provided this will enable or disable bold mode. + :param dim: if provided this will enable or disable dim mode. This is + badly supported. + :param underline: if provided this will enable or disable underline. + :param blink: if provided this will enable or disable blinking. + :param reverse: if provided this will enable or disable inverse + rendering (foreground becomes background and the + other way round). + :param reset: by default a reset-all code is added at the end of the + string which means that styles do not carry over. This + can be disabled to compose styles. + """ + bits = [] + if fg: + try: + bits.append("\033[{}m".format(_ansi_colors[fg])) + except KeyError: + raise TypeError("Unknown color '{}'".format(fg)) + if bg: + try: + bits.append("\033[{}m".format(_ansi_colors[bg] + 10)) + except KeyError: + raise TypeError("Unknown color '{}'".format(bg)) + if bold is not None: + bits.append("\033[{}m".format(1 if bold else 22)) + if dim is not None: + bits.append("\033[{}m".format(2 if dim else 22)) + if underline is not None: + bits.append("\033[{}m".format(4 if underline else 24)) + if blink is not None: + bits.append("\033[{}m".format(5 if blink else 25)) + if reverse is not None: + bits.append("\033[{}m".format(7 if reverse else 27)) + bits.append(text) + if reset: + bits.append(_ansi_reset_all) + return "".join(bits) + + +def unstyle(text): + """Removes ANSI styling information from a string. Usually it's not + necessary to use this function as Click's echo function will + automatically remove styling if necessary. + + .. versionadded:: 2.0 + + :param text: the text to remove style information from. + """ + return strip_ansi(text) + + +def secho(message=None, file=None, nl=True, err=False, color=None, **styles): + """This function combines :func:`echo` and :func:`style` into one + call. As such the following two calls are the same:: + + click.secho('Hello World!', fg='green') + click.echo(click.style('Hello World!', fg='green')) + + All keyword arguments are forwarded to the underlying functions + depending on which one they go with. + + .. versionadded:: 2.0 + """ + if message is not None: + message = style(message, **styles) + return echo(message, file=file, nl=nl, err=err, color=color) + + +def edit( + text=None, editor=None, env=None, require_save=True, extension=".txt", filename=None +): + r"""Edits the given text in the defined editor. If an editor is given + (should be the full path to the executable but the regular operating + system search path is used for finding the executable) it overrides + the detected editor. Optionally, some environment variables can be + used. If the editor is closed without changes, `None` is returned. In + case a file is edited directly the return value is always `None` and + `require_save` and `extension` are ignored. + + If the editor cannot be opened a :exc:`UsageError` is raised. + + Note for Windows: to simplify cross-platform usage, the newlines are + automatically converted from POSIX to Windows and vice versa. As such, + the message here will have ``\n`` as newline markers. + + :param text: the text to edit. + :param editor: optionally the editor to use. Defaults to automatic + detection. + :param env: environment variables to forward to the editor. + :param require_save: if this is true, then not saving in the editor + will make the return value become `None`. + :param extension: the extension to tell the editor about. This defaults + to `.txt` but changing this might change syntax + highlighting. + :param filename: if provided it will edit this file instead of the + provided text contents. It will not use a temporary + file as an indirection in that case. + """ + from ._termui_impl import Editor + + editor = Editor( + editor=editor, env=env, require_save=require_save, extension=extension + ) + if filename is None: + return editor.edit(text) + editor.edit_file(filename) + + +def launch(url, wait=False, locate=False): + """This function launches the given URL (or filename) in the default + viewer application for this file type. If this is an executable, it + might launch the executable in a new session. The return value is + the exit code of the launched application. Usually, ``0`` indicates + success. + + Examples:: + + click.launch('https://click.palletsprojects.com/') + click.launch('/my/downloaded/file', locate=True) + + .. versionadded:: 2.0 + + :param url: URL or filename of the thing to launch. + :param wait: waits for the program to stop. + :param locate: if this is set to `True` then instead of launching the + application associated with the URL it will attempt to + launch a file manager with the file located. This + might have weird effects if the URL does not point to + the filesystem. + """ + from ._termui_impl import open_url + + return open_url(url, wait=wait, locate=locate) + + +# If this is provided, getchar() calls into this instead. This is used +# for unittesting purposes. +_getchar = None + + +def getchar(echo=False): + """Fetches a single character from the terminal and returns it. This + will always return a unicode character and under certain rare + circumstances this might return more than one character. The + situations which more than one character is returned is when for + whatever reason multiple characters end up in the terminal buffer or + standard input was not actually a terminal. + + Note that this will always read from the terminal, even if something + is piped into the standard input. + + Note for Windows: in rare cases when typing non-ASCII characters, this + function might wait for a second character and then return both at once. + This is because certain Unicode characters look like special-key markers. + + .. versionadded:: 2.0 + + :param echo: if set to `True`, the character read will also show up on + the terminal. The default is to not show it. + """ + f = _getchar + if f is None: + from ._termui_impl import getchar as f + return f(echo) + + +def raw_terminal(): + from ._termui_impl import raw_terminal as f + + return f() + + +def pause(info="Press any key to continue ...", err=False): + """This command stops execution and waits for the user to press any + key to continue. This is similar to the Windows batch "pause" + command. If the program is not run through a terminal, this command + will instead do nothing. + + .. versionadded:: 2.0 + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param info: the info string to print before pausing. + :param err: if set to message goes to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + if not isatty(sys.stdin) or not isatty(sys.stdout): + return + try: + if info: + echo(info, nl=False, err=err) + try: + getchar() + except (KeyboardInterrupt, EOFError): + pass + finally: + if info: + echo(err=err) diff --git a/robot/lib/python3.8/site-packages/click/testing.py b/robot/lib/python3.8/site-packages/click/testing.py new file mode 100644 index 0000000000000000000000000000000000000000..a3dba3b3014b61ca507d191e1f83488eda85cfff --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/testing.py @@ -0,0 +1,382 @@ +import contextlib +import os +import shlex +import shutil +import sys +import tempfile + +from . import formatting +from . import termui +from . import utils +from ._compat import iteritems +from ._compat import PY2 +from ._compat import string_types + + +if PY2: + from cStringIO import StringIO +else: + import io + from ._compat import _find_binary_reader + + +class EchoingStdin(object): + def __init__(self, input, output): + self._input = input + self._output = output + + def __getattr__(self, x): + return getattr(self._input, x) + + def _echo(self, rv): + self._output.write(rv) + return rv + + def read(self, n=-1): + return self._echo(self._input.read(n)) + + def readline(self, n=-1): + return self._echo(self._input.readline(n)) + + def readlines(self): + return [self._echo(x) for x in self._input.readlines()] + + def __iter__(self): + return iter(self._echo(x) for x in self._input) + + def __repr__(self): + return repr(self._input) + + +def make_input_stream(input, charset): + # Is already an input stream. + if hasattr(input, "read"): + if PY2: + return input + rv = _find_binary_reader(input) + if rv is not None: + return rv + raise TypeError("Could not find binary reader for input stream.") + + if input is None: + input = b"" + elif not isinstance(input, bytes): + input = input.encode(charset) + if PY2: + return StringIO(input) + return io.BytesIO(input) + + +class Result(object): + """Holds the captured result of an invoked CLI script.""" + + def __init__( + self, runner, stdout_bytes, stderr_bytes, exit_code, exception, exc_info=None + ): + #: The runner that created the result + self.runner = runner + #: The standard output as bytes. + self.stdout_bytes = stdout_bytes + #: The standard error as bytes, or None if not available + self.stderr_bytes = stderr_bytes + #: The exit code as integer. + self.exit_code = exit_code + #: The exception that happened if one did. + self.exception = exception + #: The traceback + self.exc_info = exc_info + + @property + def output(self): + """The (standard) output as unicode string.""" + return self.stdout + + @property + def stdout(self): + """The standard output as unicode string.""" + return self.stdout_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + @property + def stderr(self): + """The standard error as unicode string.""" + if self.stderr_bytes is None: + raise ValueError("stderr not separately captured") + return self.stderr_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + def __repr__(self): + return "<{} {}>".format( + type(self).__name__, repr(self.exception) if self.exception else "okay" + ) + + +class CliRunner(object): + """The CLI runner provides functionality to invoke a Click command line + script for unittesting purposes in a isolated environment. This only + works in single-threaded systems without any concurrency as it changes the + global interpreter state. + + :param charset: the character set for the input and output data. This is + UTF-8 by default and should not be changed currently as + the reporting to Click only works in Python 2 properly. + :param env: a dictionary with environment variables for overriding. + :param echo_stdin: if this is set to `True`, then reading from stdin writes + to stdout. This is useful for showing examples in + some circumstances. Note that regular prompts + will automatically echo the input. + :param mix_stderr: if this is set to `False`, then stdout and stderr are + preserved as independent streams. This is useful for + Unix-philosophy apps that have predictable stdout and + noisy stderr, such that each may be measured + independently + """ + + def __init__(self, charset=None, env=None, echo_stdin=False, mix_stderr=True): + if charset is None: + charset = "utf-8" + self.charset = charset + self.env = env or {} + self.echo_stdin = echo_stdin + self.mix_stderr = mix_stderr + + def get_default_prog_name(self, cli): + """Given a command object it will return the default program name + for it. The default is the `name` attribute or ``"root"`` if not + set. + """ + return cli.name or "root" + + def make_env(self, overrides=None): + """Returns the environment overrides for invoking a script.""" + rv = dict(self.env) + if overrides: + rv.update(overrides) + return rv + + @contextlib.contextmanager + def isolation(self, input=None, env=None, color=False): + """A context manager that sets up the isolation for invoking of a + command line tool. This sets up stdin with the given input data + and `os.environ` with the overrides from the given dictionary. + This also rebinds some internals in Click to be mocked (like the + prompt functionality). + + This is automatically done in the :meth:`invoke` method. + + .. versionadded:: 4.0 + The ``color`` parameter was added. + + :param input: the input stream to put into sys.stdin. + :param env: the environment overrides as dictionary. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + """ + input = make_input_stream(input, self.charset) + + old_stdin = sys.stdin + old_stdout = sys.stdout + old_stderr = sys.stderr + old_forced_width = formatting.FORCED_WIDTH + formatting.FORCED_WIDTH = 80 + + env = self.make_env(env) + + if PY2: + bytes_output = StringIO() + if self.echo_stdin: + input = EchoingStdin(input, bytes_output) + sys.stdout = bytes_output + if not self.mix_stderr: + bytes_error = StringIO() + sys.stderr = bytes_error + else: + bytes_output = io.BytesIO() + if self.echo_stdin: + input = EchoingStdin(input, bytes_output) + input = io.TextIOWrapper(input, encoding=self.charset) + sys.stdout = io.TextIOWrapper(bytes_output, encoding=self.charset) + if not self.mix_stderr: + bytes_error = io.BytesIO() + sys.stderr = io.TextIOWrapper(bytes_error, encoding=self.charset) + + if self.mix_stderr: + sys.stderr = sys.stdout + + sys.stdin = input + + def visible_input(prompt=None): + sys.stdout.write(prompt or "") + val = input.readline().rstrip("\r\n") + sys.stdout.write("{}\n".format(val)) + sys.stdout.flush() + return val + + def hidden_input(prompt=None): + sys.stdout.write("{}\n".format(prompt or "")) + sys.stdout.flush() + return input.readline().rstrip("\r\n") + + def _getchar(echo): + char = sys.stdin.read(1) + if echo: + sys.stdout.write(char) + sys.stdout.flush() + return char + + default_color = color + + def should_strip_ansi(stream=None, color=None): + if color is None: + return not default_color + return not color + + old_visible_prompt_func = termui.visible_prompt_func + old_hidden_prompt_func = termui.hidden_prompt_func + old__getchar_func = termui._getchar + old_should_strip_ansi = utils.should_strip_ansi + termui.visible_prompt_func = visible_input + termui.hidden_prompt_func = hidden_input + termui._getchar = _getchar + utils.should_strip_ansi = should_strip_ansi + + old_env = {} + try: + for key, value in iteritems(env): + old_env[key] = os.environ.get(key) + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + yield (bytes_output, not self.mix_stderr and bytes_error) + finally: + for key, value in iteritems(old_env): + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + sys.stdout = old_stdout + sys.stderr = old_stderr + sys.stdin = old_stdin + termui.visible_prompt_func = old_visible_prompt_func + termui.hidden_prompt_func = old_hidden_prompt_func + termui._getchar = old__getchar_func + utils.should_strip_ansi = old_should_strip_ansi + formatting.FORCED_WIDTH = old_forced_width + + def invoke( + self, + cli, + args=None, + input=None, + env=None, + catch_exceptions=True, + color=False, + **extra + ): + """Invokes a command in an isolated environment. The arguments are + forwarded directly to the command line script, the `extra` keyword + arguments are passed to the :meth:`~clickpkg.Command.main` function of + the command. + + This returns a :class:`Result` object. + + .. versionadded:: 3.0 + The ``catch_exceptions`` parameter was added. + + .. versionchanged:: 3.0 + The result object now has an `exc_info` attribute with the + traceback if available. + + .. versionadded:: 4.0 + The ``color`` parameter was added. + + :param cli: the command to invoke + :param args: the arguments to invoke. It may be given as an iterable + or a string. When given as string it will be interpreted + as a Unix shell command. More details at + :func:`shlex.split`. + :param input: the input data for `sys.stdin`. + :param env: the environment overrides. + :param catch_exceptions: Whether to catch any other exceptions than + ``SystemExit``. + :param extra: the keyword arguments to pass to :meth:`main`. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + """ + exc_info = None + with self.isolation(input=input, env=env, color=color) as outstreams: + exception = None + exit_code = 0 + + if isinstance(args, string_types): + args = shlex.split(args) + + try: + prog_name = extra.pop("prog_name") + except KeyError: + prog_name = self.get_default_prog_name(cli) + + try: + cli.main(args=args or (), prog_name=prog_name, **extra) + except SystemExit as e: + exc_info = sys.exc_info() + exit_code = e.code + if exit_code is None: + exit_code = 0 + + if exit_code != 0: + exception = e + + if not isinstance(exit_code, int): + sys.stdout.write(str(exit_code)) + sys.stdout.write("\n") + exit_code = 1 + + except Exception as e: + if not catch_exceptions: + raise + exception = e + exit_code = 1 + exc_info = sys.exc_info() + finally: + sys.stdout.flush() + stdout = outstreams[0].getvalue() + if self.mix_stderr: + stderr = None + else: + stderr = outstreams[1].getvalue() + + return Result( + runner=self, + stdout_bytes=stdout, + stderr_bytes=stderr, + exit_code=exit_code, + exception=exception, + exc_info=exc_info, + ) + + @contextlib.contextmanager + def isolated_filesystem(self): + """A context manager that creates a temporary folder and changes + the current working directory to it for isolated filesystem tests. + """ + cwd = os.getcwd() + t = tempfile.mkdtemp() + os.chdir(t) + try: + yield t + finally: + os.chdir(cwd) + try: + shutil.rmtree(t) + except (OSError, IOError): # noqa: B014 + pass diff --git a/robot/lib/python3.8/site-packages/click/types.py b/robot/lib/python3.8/site-packages/click/types.py new file mode 100644 index 0000000000000000000000000000000000000000..505c39f850922af375a12b130754e9ad6fb7fd02 --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/types.py @@ -0,0 +1,762 @@ +import os +import stat +from datetime import datetime + +from ._compat import _get_argv_encoding +from ._compat import filename_to_ui +from ._compat import get_filesystem_encoding +from ._compat import get_streerror +from ._compat import open_stream +from ._compat import PY2 +from ._compat import text_type +from .exceptions import BadParameter +from .utils import LazyFile +from .utils import safecall + + +class ParamType(object): + """Helper for converting values through types. The following is + necessary for a valid type: + + * it needs a name + * it needs to pass through None unchanged + * it needs to convert from a string + * it needs to convert its result type through unchanged + (eg: needs to be idempotent) + * it needs to be able to deal with param and context being `None`. + This can be the case when the object is used with prompt + inputs. + """ + + is_composite = False + + #: the descriptive name of this type + name = None + + #: if a list of this type is expected and the value is pulled from a + #: string environment variable, this is what splits it up. `None` + #: means any whitespace. For all parameters the general rule is that + #: whitespace splits them up. The exception are paths and files which + #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on + #: Windows). + envvar_list_splitter = None + + def __call__(self, value, param=None, ctx=None): + if value is not None: + return self.convert(value, param, ctx) + + def get_metavar(self, param): + """Returns the metavar default for this param if it provides one.""" + + def get_missing_message(self, param): + """Optionally might return extra information about a missing + parameter. + + .. versionadded:: 2.0 + """ + + def convert(self, value, param, ctx): + """Converts the value. This is not invoked for values that are + `None` (the missing value). + """ + return value + + def split_envvar_value(self, rv): + """Given a value from an environment variable this splits it up + into small chunks depending on the defined envvar list splitter. + + If the splitter is set to `None`, which means that whitespace splits, + then leading and trailing whitespace is ignored. Otherwise, leading + and trailing splitters usually lead to empty items being included. + """ + return (rv or "").split(self.envvar_list_splitter) + + def fail(self, message, param=None, ctx=None): + """Helper method to fail with an invalid value message.""" + raise BadParameter(message, ctx=ctx, param=param) + + +class CompositeParamType(ParamType): + is_composite = True + + @property + def arity(self): + raise NotImplementedError() + + +class FuncParamType(ParamType): + def __init__(self, func): + self.name = func.__name__ + self.func = func + + def convert(self, value, param, ctx): + try: + return self.func(value) + except ValueError: + try: + value = text_type(value) + except UnicodeError: + value = str(value).decode("utf-8", "replace") + self.fail(value, param, ctx) + + +class UnprocessedParamType(ParamType): + name = "text" + + def convert(self, value, param, ctx): + return value + + def __repr__(self): + return "UNPROCESSED" + + +class StringParamType(ParamType): + name = "text" + + def convert(self, value, param, ctx): + if isinstance(value, bytes): + enc = _get_argv_encoding() + try: + value = value.decode(enc) + except UnicodeError: + fs_enc = get_filesystem_encoding() + if fs_enc != enc: + try: + value = value.decode(fs_enc) + except UnicodeError: + value = value.decode("utf-8", "replace") + else: + value = value.decode("utf-8", "replace") + return value + return value + + def __repr__(self): + return "STRING" + + +class Choice(ParamType): + """The choice type allows a value to be checked against a fixed set + of supported values. All of these values have to be strings. + + You should only pass a list or tuple of choices. Other iterables + (like generators) may lead to surprising results. + + The resulting value will always be one of the originally passed choices + regardless of ``case_sensitive`` or any ``ctx.token_normalize_func`` + being specified. + + See :ref:`choice-opts` for an example. + + :param case_sensitive: Set to false to make choices case + insensitive. Defaults to true. + """ + + name = "choice" + + def __init__(self, choices, case_sensitive=True): + self.choices = choices + self.case_sensitive = case_sensitive + + def get_metavar(self, param): + return "[{}]".format("|".join(self.choices)) + + def get_missing_message(self, param): + return "Choose from:\n\t{}.".format(",\n\t".join(self.choices)) + + def convert(self, value, param, ctx): + # Match through normalization and case sensitivity + # first do token_normalize_func, then lowercase + # preserve original `value` to produce an accurate message in + # `self.fail` + normed_value = value + normed_choices = {choice: choice for choice in self.choices} + + if ctx is not None and ctx.token_normalize_func is not None: + normed_value = ctx.token_normalize_func(value) + normed_choices = { + ctx.token_normalize_func(normed_choice): original + for normed_choice, original in normed_choices.items() + } + + if not self.case_sensitive: + if PY2: + lower = str.lower + else: + lower = str.casefold + + normed_value = lower(normed_value) + normed_choices = { + lower(normed_choice): original + for normed_choice, original in normed_choices.items() + } + + if normed_value in normed_choices: + return normed_choices[normed_value] + + self.fail( + "invalid choice: {}. (choose from {})".format( + value, ", ".join(self.choices) + ), + param, + ctx, + ) + + def __repr__(self): + return "Choice('{}')".format(list(self.choices)) + + +class DateTime(ParamType): + """The DateTime type converts date strings into `datetime` objects. + + The format strings which are checked are configurable, but default to some + common (non-timezone aware) ISO 8601 formats. + + When specifying *DateTime* formats, you should only pass a list or a tuple. + Other iterables, like generators, may lead to surprising results. + + The format strings are processed using ``datetime.strptime``, and this + consequently defines the format strings which are allowed. + + Parsing is tried using each format, in order, and the first format which + parses successfully is used. + + :param formats: A list or tuple of date format strings, in the order in + which they should be tried. Defaults to + ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``, + ``'%Y-%m-%d %H:%M:%S'``. + """ + + name = "datetime" + + def __init__(self, formats=None): + self.formats = formats or ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"] + + def get_metavar(self, param): + return "[{}]".format("|".join(self.formats)) + + def _try_to_convert_date(self, value, format): + try: + return datetime.strptime(value, format) + except ValueError: + return None + + def convert(self, value, param, ctx): + # Exact match + for format in self.formats: + dtime = self._try_to_convert_date(value, format) + if dtime: + return dtime + + self.fail( + "invalid datetime format: {}. (choose from {})".format( + value, ", ".join(self.formats) + ) + ) + + def __repr__(self): + return "DateTime" + + +class IntParamType(ParamType): + name = "integer" + + def convert(self, value, param, ctx): + try: + return int(value) + except ValueError: + self.fail("{} is not a valid integer".format(value), param, ctx) + + def __repr__(self): + return "INT" + + +class IntRange(IntParamType): + """A parameter that works similar to :data:`click.INT` but restricts + the value to fit into a range. The default behavior is to fail if the + value falls outside the range, but it can also be silently clamped + between the two edges. + + See :ref:`ranges` for an example. + """ + + name = "integer range" + + def __init__(self, min=None, max=None, clamp=False): + self.min = min + self.max = max + self.clamp = clamp + + def convert(self, value, param, ctx): + rv = IntParamType.convert(self, value, param, ctx) + if self.clamp: + if self.min is not None and rv < self.min: + return self.min + if self.max is not None and rv > self.max: + return self.max + if ( + self.min is not None + and rv < self.min + or self.max is not None + and rv > self.max + ): + if self.min is None: + self.fail( + "{} is bigger than the maximum valid value {}.".format( + rv, self.max + ), + param, + ctx, + ) + elif self.max is None: + self.fail( + "{} is smaller than the minimum valid value {}.".format( + rv, self.min + ), + param, + ctx, + ) + else: + self.fail( + "{} is not in the valid range of {} to {}.".format( + rv, self.min, self.max + ), + param, + ctx, + ) + return rv + + def __repr__(self): + return "IntRange({}, {})".format(self.min, self.max) + + +class FloatParamType(ParamType): + name = "float" + + def convert(self, value, param, ctx): + try: + return float(value) + except ValueError: + self.fail( + "{} is not a valid floating point value".format(value), param, ctx + ) + + def __repr__(self): + return "FLOAT" + + +class FloatRange(FloatParamType): + """A parameter that works similar to :data:`click.FLOAT` but restricts + the value to fit into a range. The default behavior is to fail if the + value falls outside the range, but it can also be silently clamped + between the two edges. + + See :ref:`ranges` for an example. + """ + + name = "float range" + + def __init__(self, min=None, max=None, clamp=False): + self.min = min + self.max = max + self.clamp = clamp + + def convert(self, value, param, ctx): + rv = FloatParamType.convert(self, value, param, ctx) + if self.clamp: + if self.min is not None and rv < self.min: + return self.min + if self.max is not None and rv > self.max: + return self.max + if ( + self.min is not None + and rv < self.min + or self.max is not None + and rv > self.max + ): + if self.min is None: + self.fail( + "{} is bigger than the maximum valid value {}.".format( + rv, self.max + ), + param, + ctx, + ) + elif self.max is None: + self.fail( + "{} is smaller than the minimum valid value {}.".format( + rv, self.min + ), + param, + ctx, + ) + else: + self.fail( + "{} is not in the valid range of {} to {}.".format( + rv, self.min, self.max + ), + param, + ctx, + ) + return rv + + def __repr__(self): + return "FloatRange({}, {})".format(self.min, self.max) + + +class BoolParamType(ParamType): + name = "boolean" + + def convert(self, value, param, ctx): + if isinstance(value, bool): + return bool(value) + value = value.lower() + if value in ("true", "t", "1", "yes", "y"): + return True + elif value in ("false", "f", "0", "no", "n"): + return False + self.fail("{} is not a valid boolean".format(value), param, ctx) + + def __repr__(self): + return "BOOL" + + +class UUIDParameterType(ParamType): + name = "uuid" + + def convert(self, value, param, ctx): + import uuid + + try: + if PY2 and isinstance(value, text_type): + value = value.encode("ascii") + return uuid.UUID(value) + except ValueError: + self.fail("{} is not a valid UUID value".format(value), param, ctx) + + def __repr__(self): + return "UUID" + + +class File(ParamType): + """Declares a parameter to be a file for reading or writing. The file + is automatically closed once the context tears down (after the command + finished working). + + Files can be opened for reading or writing. The special value ``-`` + indicates stdin or stdout depending on the mode. + + By default, the file is opened for reading text data, but it can also be + opened in binary mode or for writing. The encoding parameter can be used + to force a specific encoding. + + The `lazy` flag controls if the file should be opened immediately or upon + first IO. The default is to be non-lazy for standard input and output + streams as well as files opened for reading, `lazy` otherwise. When opening a + file lazily for reading, it is still opened temporarily for validation, but + will not be held open until first IO. lazy is mainly useful when opening + for writing to avoid creating the file until it is needed. + + Starting with Click 2.0, files can also be opened atomically in which + case all writes go into a separate file in the same folder and upon + completion the file will be moved over to the original location. This + is useful if a file regularly read by other users is modified. + + See :ref:`file-args` for more information. + """ + + name = "filename" + envvar_list_splitter = os.path.pathsep + + def __init__( + self, mode="r", encoding=None, errors="strict", lazy=None, atomic=False + ): + self.mode = mode + self.encoding = encoding + self.errors = errors + self.lazy = lazy + self.atomic = atomic + + def resolve_lazy_flag(self, value): + if self.lazy is not None: + return self.lazy + if value == "-": + return False + elif "w" in self.mode: + return True + return False + + def convert(self, value, param, ctx): + try: + if hasattr(value, "read") or hasattr(value, "write"): + return value + + lazy = self.resolve_lazy_flag(value) + + if lazy: + f = LazyFile( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + if ctx is not None: + ctx.call_on_close(f.close_intelligently) + return f + + f, should_close = open_stream( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + # If a context is provided, we automatically close the file + # at the end of the context execution (or flush out). If a + # context does not exist, it's the caller's responsibility to + # properly close the file. This for instance happens when the + # type is used with prompts. + if ctx is not None: + if should_close: + ctx.call_on_close(safecall(f.close)) + else: + ctx.call_on_close(safecall(f.flush)) + return f + except (IOError, OSError) as e: # noqa: B014 + self.fail( + "Could not open file: {}: {}".format( + filename_to_ui(value), get_streerror(e) + ), + param, + ctx, + ) + + +class Path(ParamType): + """The path type is similar to the :class:`File` type but it performs + different checks. First of all, instead of returning an open file + handle it returns just the filename. Secondly, it can perform various + basic checks about what the file or directory should be. + + .. versionchanged:: 6.0 + `allow_dash` was added. + + :param exists: if set to true, the file or directory needs to exist for + this value to be valid. If this is not required and a + file does indeed not exist, then all further checks are + silently skipped. + :param file_okay: controls if a file is a possible value. + :param dir_okay: controls if a directory is a possible value. + :param writable: if true, a writable check is performed. + :param readable: if true, a readable check is performed. + :param resolve_path: if this is true, then the path is fully resolved + before the value is passed onwards. This means + that it's absolute and symlinks are resolved. It + will not expand a tilde-prefix, as this is + supposed to be done by the shell only. + :param allow_dash: If this is set to `True`, a single dash to indicate + standard streams is permitted. + :param path_type: optionally a string type that should be used to + represent the path. The default is `None` which + means the return value will be either bytes or + unicode depending on what makes most sense given the + input data Click deals with. + """ + + envvar_list_splitter = os.path.pathsep + + def __init__( + self, + exists=False, + file_okay=True, + dir_okay=True, + writable=False, + readable=True, + resolve_path=False, + allow_dash=False, + path_type=None, + ): + self.exists = exists + self.file_okay = file_okay + self.dir_okay = dir_okay + self.writable = writable + self.readable = readable + self.resolve_path = resolve_path + self.allow_dash = allow_dash + self.type = path_type + + if self.file_okay and not self.dir_okay: + self.name = "file" + self.path_type = "File" + elif self.dir_okay and not self.file_okay: + self.name = "directory" + self.path_type = "Directory" + else: + self.name = "path" + self.path_type = "Path" + + def coerce_path_result(self, rv): + if self.type is not None and not isinstance(rv, self.type): + if self.type is text_type: + rv = rv.decode(get_filesystem_encoding()) + else: + rv = rv.encode(get_filesystem_encoding()) + return rv + + def convert(self, value, param, ctx): + rv = value + + is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-") + + if not is_dash: + if self.resolve_path: + rv = os.path.realpath(rv) + + try: + st = os.stat(rv) + except OSError: + if not self.exists: + return self.coerce_path_result(rv) + self.fail( + "{} '{}' does not exist.".format( + self.path_type, filename_to_ui(value) + ), + param, + ctx, + ) + + if not self.file_okay and stat.S_ISREG(st.st_mode): + self.fail( + "{} '{}' is a file.".format(self.path_type, filename_to_ui(value)), + param, + ctx, + ) + if not self.dir_okay and stat.S_ISDIR(st.st_mode): + self.fail( + "{} '{}' is a directory.".format( + self.path_type, filename_to_ui(value) + ), + param, + ctx, + ) + if self.writable and not os.access(value, os.W_OK): + self.fail( + "{} '{}' is not writable.".format( + self.path_type, filename_to_ui(value) + ), + param, + ctx, + ) + if self.readable and not os.access(value, os.R_OK): + self.fail( + "{} '{}' is not readable.".format( + self.path_type, filename_to_ui(value) + ), + param, + ctx, + ) + + return self.coerce_path_result(rv) + + +class Tuple(CompositeParamType): + """The default behavior of Click is to apply a type on a value directly. + This works well in most cases, except for when `nargs` is set to a fixed + count and different types should be used for different items. In this + case the :class:`Tuple` type can be used. This type can only be used + if `nargs` is set to a fixed number. + + For more information see :ref:`tuple-type`. + + This can be selected by using a Python tuple literal as a type. + + :param types: a list of types that should be used for the tuple items. + """ + + def __init__(self, types): + self.types = [convert_type(ty) for ty in types] + + @property + def name(self): + return "<{}>".format(" ".join(ty.name for ty in self.types)) + + @property + def arity(self): + return len(self.types) + + def convert(self, value, param, ctx): + if len(value) != len(self.types): + raise TypeError( + "It would appear that nargs is set to conflict with the" + " composite type arity." + ) + return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value)) + + +def convert_type(ty, default=None): + """Converts a callable or python type into the most appropriate + param type. + """ + guessed_type = False + if ty is None and default is not None: + if isinstance(default, tuple): + ty = tuple(map(type, default)) + else: + ty = type(default) + guessed_type = True + + if isinstance(ty, tuple): + return Tuple(ty) + if isinstance(ty, ParamType): + return ty + if ty is text_type or ty is str or ty is None: + return STRING + if ty is int: + return INT + # Booleans are only okay if not guessed. This is done because for + # flags the default value is actually a bit of a lie in that it + # indicates which of the flags is the one we want. See get_default() + # for more information. + if ty is bool and not guessed_type: + return BOOL + if ty is float: + return FLOAT + if guessed_type: + return STRING + + # Catch a common mistake + if __debug__: + try: + if issubclass(ty, ParamType): + raise AssertionError( + "Attempted to use an uninstantiated parameter type ({}).".format(ty) + ) + except TypeError: + pass + return FuncParamType(ty) + + +#: A dummy parameter type that just does nothing. From a user's +#: perspective this appears to just be the same as `STRING` but internally +#: no string conversion takes place. This is necessary to achieve the +#: same bytes/unicode behavior on Python 2/3 in situations where you want +#: to not convert argument types. This is usually useful when working +#: with file paths as they can appear in bytes and unicode. +#: +#: For path related uses the :class:`Path` type is a better choice but +#: there are situations where an unprocessed type is useful which is why +#: it is is provided. +#: +#: .. versionadded:: 4.0 +UNPROCESSED = UnprocessedParamType() + +#: A unicode string parameter type which is the implicit default. This +#: can also be selected by using ``str`` as type. +STRING = StringParamType() + +#: An integer parameter. This can also be selected by using ``int`` as +#: type. +INT = IntParamType() + +#: A floating point value parameter. This can also be selected by using +#: ``float`` as type. +FLOAT = FloatParamType() + +#: A boolean parameter. This is the default for boolean flags. This can +#: also be selected by using ``bool`` as a type. +BOOL = BoolParamType() + +#: A UUID parameter. +UUID = UUIDParameterType() diff --git a/robot/lib/python3.8/site-packages/click/utils.py b/robot/lib/python3.8/site-packages/click/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..79265e732d49a4ea35198565a8d1bc401e07b7ea --- /dev/null +++ b/robot/lib/python3.8/site-packages/click/utils.py @@ -0,0 +1,455 @@ +import os +import sys + +from ._compat import _default_text_stderr +from ._compat import _default_text_stdout +from ._compat import auto_wrap_for_ansi +from ._compat import binary_streams +from ._compat import filename_to_ui +from ._compat import get_filesystem_encoding +from ._compat import get_streerror +from ._compat import is_bytes +from ._compat import open_stream +from ._compat import PY2 +from ._compat import should_strip_ansi +from ._compat import string_types +from ._compat import strip_ansi +from ._compat import text_streams +from ._compat import text_type +from ._compat import WIN +from .globals import resolve_color_default + +if not PY2: + from ._compat import _find_binary_writer +elif WIN: + from ._winconsole import _get_windows_argv + from ._winconsole import _hash_py_argv + from ._winconsole import _initial_argv_hash + +echo_native_types = string_types + (bytes, bytearray) + + +def _posixify(name): + return "-".join(name.split()).lower() + + +def safecall(func): + """Wraps a function so that it swallows exceptions.""" + + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception: + pass + + return wrapper + + +def make_str(value): + """Converts a value into a valid string.""" + if isinstance(value, bytes): + try: + return value.decode(get_filesystem_encoding()) + except UnicodeError: + return value.decode("utf-8", "replace") + return text_type(value) + + +def make_default_short_help(help, max_length=45): + """Return a condensed version of help string.""" + words = help.split() + total_length = 0 + result = [] + done = False + + for word in words: + if word[-1:] == ".": + done = True + new_length = 1 + len(word) if result else len(word) + if total_length + new_length > max_length: + result.append("...") + done = True + else: + if result: + result.append(" ") + result.append(word) + if done: + break + total_length += new_length + + return "".join(result) + + +class LazyFile(object): + """A lazy file works like a regular file but it does not fully open + the file but it does perform some basic checks early to see if the + filename parameter does make sense. This is useful for safely opening + files for writing. + """ + + def __init__( + self, filename, mode="r", encoding=None, errors="strict", atomic=False + ): + self.name = filename + self.mode = mode + self.encoding = encoding + self.errors = errors + self.atomic = atomic + + if filename == "-": + self._f, self.should_close = open_stream(filename, mode, encoding, errors) + else: + if "r" in mode: + # Open and close the file in case we're opening it for + # reading so that we can catch at least some errors in + # some cases early. + open(filename, mode).close() + self._f = None + self.should_close = True + + def __getattr__(self, name): + return getattr(self.open(), name) + + def __repr__(self): + if self._f is not None: + return repr(self._f) + return "".format(self.name, self.mode) + + def open(self): + """Opens the file if it's not yet open. This call might fail with + a :exc:`FileError`. Not handling this error will produce an error + that Click shows. + """ + if self._f is not None: + return self._f + try: + rv, self.should_close = open_stream( + self.name, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + except (IOError, OSError) as e: # noqa: E402 + from .exceptions import FileError + + raise FileError(self.name, hint=get_streerror(e)) + self._f = rv + return rv + + def close(self): + """Closes the underlying file, no matter what.""" + if self._f is not None: + self._f.close() + + def close_intelligently(self): + """This function only closes the file if it was opened by the lazy + file wrapper. For instance this will never close stdin. + """ + if self.should_close: + self.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + self.close_intelligently() + + def __iter__(self): + self.open() + return iter(self._f) + + +class KeepOpenFile(object): + def __init__(self, file): + self._file = file + + def __getattr__(self, name): + return getattr(self._file, name) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + pass + + def __repr__(self): + return repr(self._file) + + def __iter__(self): + return iter(self._file) + + +def echo(message=None, file=None, nl=True, err=False, color=None): + """Prints a message plus a newline to the given file or stdout. On + first sight, this looks like the print function, but it has improved + support for handling Unicode and binary data that does not fail no + matter how badly configured the system is. + + Primarily it means that you can print binary data as well as Unicode + data on both 2.x and 3.x to the given file in the most appropriate way + possible. This is a very carefree function in that it will try its + best to not fail. As of Click 6.0 this includes support for unicode + output on the Windows console. + + In addition to that, if `colorama`_ is installed, the echo function will + also support clever handling of ANSI codes. Essentially it will then + do the following: + + - add transparent handling of ANSI color codes on Windows. + - hide ANSI codes automatically if the destination file is not a + terminal. + + .. _colorama: https://pypi.org/project/colorama/ + + .. versionchanged:: 6.0 + As of Click 6.0 the echo function will properly support unicode + output on the windows console. Not that click does not modify + the interpreter in any way which means that `sys.stdout` or the + print statement or function will still not provide unicode support. + + .. versionchanged:: 2.0 + Starting with version 2.0 of Click, the echo function will work + with colorama if it's installed. + + .. versionadded:: 3.0 + The `err` parameter was added. + + .. versionchanged:: 4.0 + Added the `color` flag. + + :param message: the message to print + :param file: the file to write to (defaults to ``stdout``) + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``. This is faster and easier than calling + :func:`get_text_stderr` yourself. + :param nl: if set to `True` (the default) a newline is printed afterwards. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. + """ + if file is None: + if err: + file = _default_text_stderr() + else: + file = _default_text_stdout() + + # Convert non bytes/text into the native string type. + if message is not None and not isinstance(message, echo_native_types): + message = text_type(message) + + if nl: + message = message or u"" + if isinstance(message, text_type): + message += u"\n" + else: + message += b"\n" + + # If there is a message, and we're in Python 3, and the value looks + # like bytes, we manually need to find the binary stream and write the + # message in there. This is done separately so that most stream + # types will work as you would expect. Eg: you can write to StringIO + # for other cases. + if message and not PY2 and is_bytes(message): + binary_file = _find_binary_writer(file) + if binary_file is not None: + file.flush() + binary_file.write(message) + binary_file.flush() + return + + # ANSI-style support. If there is no message or we are dealing with + # bytes nothing is happening. If we are connected to a file we want + # to strip colors. If we are on windows we either wrap the stream + # to strip the color or we use the colorama support to translate the + # ansi codes to API calls. + if message and not is_bytes(message): + color = resolve_color_default(color) + if should_strip_ansi(file, color): + message = strip_ansi(message) + elif WIN: + if auto_wrap_for_ansi is not None: + file = auto_wrap_for_ansi(file) + elif not color: + message = strip_ansi(message) + + if message: + file.write(message) + file.flush() + + +def get_binary_stream(name): + """Returns a system stream for byte processing. This essentially + returns the stream from the sys module with the given name but it + solves some compatibility issues between different Python versions. + Primarily this function is necessary for getting binary streams on + Python 3. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + """ + opener = binary_streams.get(name) + if opener is None: + raise TypeError("Unknown standard stream '{}'".format(name)) + return opener() + + +def get_text_stream(name, encoding=None, errors="strict"): + """Returns a system stream for text processing. This usually returns + a wrapped stream around a binary stream returned from + :func:`get_binary_stream` but it also can take shortcuts on Python 3 + for already correctly configured streams. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + :param encoding: overrides the detected default encoding. + :param errors: overrides the default error mode. + """ + opener = text_streams.get(name) + if opener is None: + raise TypeError("Unknown standard stream '{}'".format(name)) + return opener(encoding, errors) + + +def open_file( + filename, mode="r", encoding=None, errors="strict", lazy=False, atomic=False +): + """This is similar to how the :class:`File` works but for manual + usage. Files are opened non lazy by default. This can open regular + files as well as stdin/stdout if ``'-'`` is passed. + + If stdin/stdout is returned the stream is wrapped so that the context + manager will not close the stream accidentally. This makes it possible + to always use the function like this without having to worry to + accidentally close a standard stream:: + + with open_file(filename) as f: + ... + + .. versionadded:: 3.0 + + :param filename: the name of the file to open (or ``'-'`` for stdin/stdout). + :param mode: the mode in which to open the file. + :param encoding: the encoding to use. + :param errors: the error handling for this file. + :param lazy: can be flipped to true to open the file lazily. + :param atomic: in atomic mode writes go into a temporary file and it's + moved on close. + """ + if lazy: + return LazyFile(filename, mode, encoding, errors, atomic=atomic) + f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic) + if not should_close: + f = KeepOpenFile(f) + return f + + +def get_os_args(): + """This returns the argument part of sys.argv in the most appropriate + form for processing. What this means is that this return value is in + a format that works for Click to process but does not necessarily + correspond well to what's actually standard for the interpreter. + + On most environments the return value is ``sys.argv[:1]`` unchanged. + However if you are on Windows and running Python 2 the return value + will actually be a list of unicode strings instead because the + default behavior on that platform otherwise will not be able to + carry all possible values that sys.argv can have. + + .. versionadded:: 6.0 + """ + # We can only extract the unicode argv if sys.argv has not been + # changed since the startup of the application. + if PY2 and WIN and _initial_argv_hash == _hash_py_argv(): + return _get_windows_argv() + return sys.argv[1:] + + +def format_filename(filename, shorten=False): + """Formats a filename for user display. The main purpose of this + function is to ensure that the filename can be displayed at all. This + will decode the filename to unicode if necessary in a way that it will + not fail. Optionally, it can shorten the filename to not include the + full path to the filename. + + :param filename: formats a filename for UI display. This will also convert + the filename into unicode without failing. + :param shorten: this optionally shortens the filename to strip of the + path that leads up to it. + """ + if shorten: + filename = os.path.basename(filename) + return filename_to_ui(filename) + + +def get_app_dir(app_name, roaming=True, force_posix=False): + r"""Returns the config folder for the application. The default behavior + is to return whatever is most appropriate for the operating system. + + To give you an idea, for an app called ``"Foo Bar"``, something like + the following folders could be returned: + + Mac OS X: + ``~/Library/Application Support/Foo Bar`` + Mac OS X (POSIX): + ``~/.foo-bar`` + Unix: + ``~/.config/foo-bar`` + Unix (POSIX): + ``~/.foo-bar`` + Win XP (roaming): + ``C:\Documents and Settings\\Local Settings\Application Data\Foo Bar`` + Win XP (not roaming): + ``C:\Documents and Settings\\Application Data\Foo Bar`` + Win 7 (roaming): + ``C:\Users\\AppData\Roaming\Foo Bar`` + Win 7 (not roaming): + ``C:\Users\\AppData\Local\Foo Bar`` + + .. versionadded:: 2.0 + + :param app_name: the application name. This should be properly capitalized + and can contain whitespace. + :param roaming: controls if the folder should be roaming or not on Windows. + Has no affect otherwise. + :param force_posix: if this is set to `True` then on any POSIX system the + folder will be stored in the home folder with a leading + dot instead of the XDG config home or darwin's + application support folder. + """ + if WIN: + key = "APPDATA" if roaming else "LOCALAPPDATA" + folder = os.environ.get(key) + if folder is None: + folder = os.path.expanduser("~") + return os.path.join(folder, app_name) + if force_posix: + return os.path.join(os.path.expanduser("~/.{}".format(_posixify(app_name)))) + if sys.platform == "darwin": + return os.path.join( + os.path.expanduser("~/Library/Application Support"), app_name + ) + return os.path.join( + os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), + _posixify(app_name), + ) + + +class PacifyFlushWrapper(object): + """This wrapper is used to catch and suppress BrokenPipeErrors resulting + from ``.flush()`` being called on broken pipe during the shutdown/final-GC + of the Python interpreter. Notably ``.flush()`` is always called on + ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any + other cleanup code, and the case where the underlying file is not a broken + pipe, all calls and attributes are proxied. + """ + + def __init__(self, wrapped): + self.wrapped = wrapped + + def flush(self): + try: + self.wrapped.flush() + except IOError as e: + import errno + + if e.errno != errno.EPIPE: + raise + + def __getattr__(self, attr): + return getattr(self.wrapped, attr) diff --git a/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/AUTHORS.txt b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/AUTHORS.txt new file mode 100644 index 0000000000000000000000000000000000000000..72c87d7d38ae7bf859717c333a5ee8230f6ce624 --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/AUTHORS.txt @@ -0,0 +1,562 @@ +A_Rog +Aakanksha Agrawal <11389424+rasponic@users.noreply.github.com> +Abhinav Sagar <40603139+abhinavsagar@users.noreply.github.com> +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Tse +Adam Wentz +admin +Adrien Morison +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Aleks Bunin +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Alexey Popravka +Alli +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrey Bulgakov +Andrés Delfino <34587441+andresdelfino@users.noreply.github.com> +Andrés Delfino +Andy Freeland +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anuj Godase +AQNOUCH Mohammed +AraHaan +Arindam Choudhury +Armin Ronacher +Artem +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avner Cohen +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Darnell +Ben Hoyt +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Brandon L. Reiss +Brandt Bucher +Brett Randall +Brian Cristante <33549821+brcrista@users.noreply.github.com> +Brian Cristante +Brian Rosner +BrownTruck +Bruno Oliveira +Bruno Renié +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +c22 +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Chih-Hsuan Yen +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris McDonough +Chris Wolfe +Christian Heimes +Christian Oudard +Christopher Hunt +Christopher Snyder +Clark Boylan +Clay McClure +Cody +Cody Soyland +Colin Watson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Curtis Doty +cytolentino +Damian Quiroga +Dan Black +Dan Savilonis +Dan Sully +daniel +Daniel Collins +Daniel Hahler +Daniel Holth +Daniel Jost +Daniel Shaulov +Daniele Esposti +Daniele Procida +Danny Hermes +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Bordeynik +David Caro +David Evans +David Linke +David Pursehouse +David Tucker +David Wales +Davidovich +derwolfe +Desetude +Diego Caraballo +DiegoCaraballo +Dmitry Gladkov +Domen Kožar +Donald Stufft +Dongweiming +Douglas Thor +DrFeathers +Dustin Ingram +Dwayne Bailey +Ed Morley <501702+edmorley@users.noreply.github.com> +Ed Morley +Eitan Adler +ekristina +elainechan +Eli Schwartz +Eli Schwartz +Emil Burzo +Emil Styrke +Endoh Takanao +enoch +Erdinc Mutlu +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Ernest W Durbin III +Ernest W. Durbin III +Erwin Janssen +Eugene Vereshchagin +everdimension +Felix Yan +fiber-space +Filip Kokosiński +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gdanielson +Geoffrey Lehée +Geoffrey Sneddon +George Song +Georgi Valkov +Giftlin Rajaiah +gizmoguy1 +gkdoc <40815324+gkdoc@users.noreply.github.com> +Gopinath M <31352222+mgopi1990@users.noreply.github.com> +GOTO Hayato <3532528+gh640@users.noreply.github.com> +gpiks +Guilherme Espada +Guy Rozendorn +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +Herbert Pfennig +Hsiaoming Yang +Hugo +Hugo Lopes Tavares +Hugo van Kemenade +hugovk +Hynek Schlawack +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ilya Baryshev +INADA Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Ivan Pozdeev +Jacob Kim +jakirkham +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James Cleveland +James Cleveland +James Firth +James Polley +Jan Pokorný +Jannis Leidel +jarondl +Jason R. Coombs +Jay Graves +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jelmer Vernooij +jenix21 +Jeremy Stanley +Jeremy Zafran +Jiashuo Li +Jim Garrison +Jivan Amara +John Paton +John-Scott Atlakson +johnthagen +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joost Molenaar +Jorge Niedbalski +Joseph Long +Josh Bronson +Josh Hansen +Josh Schneier +Juanjo Bazán +Julian Berman +Julian Gethmann +Julien Demoor +jwg4 +Jyrki Pulliainen +Kai Chen +Kamal Bin Mustafa +kaustav haldar +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kexuan Sun +Kit Randel +kpinc +Krishna Oza +Kumar McMillan +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurie Opperman +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +Loren Carvalho +Lucas Cimon +Ludovic Gasc +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +László Kiss Kollár +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark Williams +Mark Williams +Markus Hametner +Masaki +Masklinn +Matej Stuchlik +Mathew Jennings +Mathieu Bridon +Matt Good +Matt Maker +Matt Robenolt +matthew +Matthew Einhorn +Matthew Gilliard +Matthew Iversen +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna <44498973+mbaluna@users.noreply.github.com> +mdebi <17590103+mdebi@users.noreply.github.com> +memoselyk +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Williamson +michaelpacer +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Monica Baluna +montefra +Monty Taylor +Nate Coraor +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nikhil Benesch +Nitesh Sharma +Nowell Strite +NtaleGrey +nvdv +Ofekmeister +ofrinevo +Oliver Jeeves +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +OMOTO Kenji +Omry Yadan +Oren Held +Oscar Benjamin +Oz N Tiram +Pachwenko <32424503+Pachwenko@users.noreply.github.com> +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavithra Eswaramoorthy <33131404+QueenCoffee@users.noreply.github.com> +Pawel Jasinski +Pekka Klärck +Peter Lisák +Peter Waller +petr-tik +Phaneendra Chiruvella +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Pratik Mallya +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal <7895pulkit@gmail.com> +Qiangning Hong +Quentin Pradet +R. David Murray +Rafael Caricio +Ralf Schmitt +Razzi Abuissa +rdb +Remi Rampin +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Richard Jones +RobberPhex +Robert Collins +Robert McGibbon +Robert T. McGibbon +robin elisha robinson +Roey Berman +Rohan Jain +Rohan Jain +Rohan Jain +Roman Bogorodskiy +Romuald Brunet +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +Roy Wellington Ⅳ +Ryan Wooden +ryneeverett +Sachi King +Salvatore Rinchiera +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sergey Vasilyev +Seth Woodworth +Shlomi Fish +Shovan Maity +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +Sorin Sbarnea +Stavros Korokithakis +Stefan Scherfke +Stephan Erb +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +stonebig +Stéphane Bidoul (ACSONE) +Stéphane Bidoul +Stéphane Klein +Sumana Harihareswara +Sviatoslav Sydorenko +Sviatoslav Sydorenko +Swat009 +Takayuki SHIMIZUKAWA +tbeswick +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tom Forbes +Tom Freudenheim +Tom V +Tomas Orsava +Tomer Chachamu +Tony Beswick +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +Travis Swicegood +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Philippon +Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> +Vitaly Babiy +Vladimir Rutsky +W. Trevor King +Wil Tan +Wilfred Hughes +William ML Leslie +William T Olson +Wilson Mo +wim glenn +Wolfgang Maier +Xavier Fernandez +Xavier Fernandez +xoviat +xtreak +YAMAMOTO Takashi +Yen Chi Hsuan +Yeray Diaz Diaz +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Zearin +Zearin +Zhiping Deng +Zvezdan Petkovic +Łukasz Langa +Семён Марьясин diff --git a/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..737fec5c5352af3d9a6a47a0670da4bdb52c5725 --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2019 The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/METADATA b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..c455cb515632088458de0546ddad104e029b64ba --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/METADATA @@ -0,0 +1,411 @@ +Metadata-Version: 2.1 +Name: colorama +Version: 0.4.3 +Summary: Cross-platform colored terminal text. +Home-page: https://github.com/tartley/colorama +Author: Jonathan Hartley +Author-email: tartley@tartley.com +Maintainer: Arnon Yaari +License: BSD +Keywords: color colour terminal text ansi windows crossplatform xplatform +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Terminals +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* + +.. image:: https://img.shields.io/pypi/v/colorama.svg + :target: https://pypi.org/project/colorama/ + :alt: Latest Version + +.. image:: https://img.shields.io/pypi/pyversions/colorama.svg + :target: https://pypi.org/project/colorama/ + :alt: Supported Python versions + +.. image:: https://travis-ci.org/tartley/colorama.svg?branch=master + :target: https://travis-ci.org/tartley/colorama + :alt: Build Status + +Download and docs: + https://pypi.org/project/colorama/ +Source code & Development: + https://github.com/tartley/colorama +Colorama for Enterprise: + https://github.com/tartley/colorama/blob/master/ENTERPRISE.md + +Description +=========== + +Makes ANSI escape character sequences (for producing colored terminal text and +cursor positioning) work under MS Windows. + +ANSI escape character sequences have long been used to produce colored terminal +text and cursor positioning on Unix and Macs. Colorama makes this work on +Windows, too, by wrapping ``stdout``, stripping ANSI sequences it finds (which +would appear as gobbledygook in the output), and converting them into the +appropriate win32 calls to modify the state of the terminal. On other platforms, +Colorama does nothing. + +Colorama also provides some shortcuts to help generate ANSI sequences +but works fine in conjunction with any other ANSI sequence generation library, +such as the venerable Termcolor (https://pypi.org/project/termcolor/) +or the fabulous Blessings (https://pypi.org/project/blessings/). + +This has the upshot of providing a simple cross-platform API for printing +colored terminal text from Python, and has the happy side-effect that existing +applications or libraries which use ANSI sequences to produce colored output on +Linux or Macs can now also work on Windows, simply by calling +``colorama.init()``. + +An alternative approach is to install ``ansi.sys`` on Windows machines, which +provides the same behaviour for all applications running in terminals. Colorama +is intended for situations where that isn't easy (e.g., maybe your app doesn't +have an installer.) + +Demo scripts in the source code repository print some colored text using +ANSI sequences. Compare their output under Gnome-terminal's built in ANSI +handling, versus on Windows Command-Prompt using Colorama: + +.. image:: https://github.com/tartley/colorama/raw/master/screenshots/ubuntu-demo.png + :width: 661 + :height: 357 + :alt: ANSI sequences on Ubuntu under gnome-terminal. + +.. image:: https://github.com/tartley/colorama/raw/master/screenshots/windows-demo.png + :width: 668 + :height: 325 + :alt: Same ANSI sequences on Windows, using Colorama. + +These screengrabs show that, on Windows, Colorama does not support ANSI 'dim +text'; it looks the same as 'normal text'. + + +License +======= + +Copyright Jonathan Hartley & Arnon Yaari, 2013. BSD 3-Clause license; see LICENSE file. + + +Dependencies +============ + +None, other than Python. Tested on Python 2.7, 3.5, 3.6, 3.7 and 3.8. + +Usage +===== + +Initialisation +-------------- + +Applications should initialise Colorama using: + +.. code-block:: python + + from colorama import init + init() + +On Windows, calling ``init()`` will filter ANSI escape sequences out of any +text sent to ``stdout`` or ``stderr``, and replace them with equivalent Win32 +calls. + +On other platforms, calling ``init()`` has no effect (unless you request other +optional functionality; see "Init Keyword Args", below). By design, this permits +applications to call ``init()`` unconditionally on all platforms, after which +ANSI output should just work. + +To stop using colorama before your program exits, simply call ``deinit()``. +This will restore ``stdout`` and ``stderr`` to their original values, so that +Colorama is disabled. To resume using Colorama again, call ``reinit()``; it is +cheaper to calling ``init()`` again (but does the same thing). + + +Colored Output +-------------- + +Cross-platform printing of colored text can then be done using Colorama's +constant shorthand for ANSI escape sequences: + +.. code-block:: python + + from colorama import Fore, Back, Style + print(Fore.RED + 'some red text') + print(Back.GREEN + 'and with a green background') + print(Style.DIM + 'and in dim text') + print(Style.RESET_ALL) + print('back to normal now') + +...or simply by manually printing ANSI sequences from your own code: + +.. code-block:: python + + print('\033[31m' + 'some red text') + print('\033[39m') # and reset to default color + +...or, Colorama can be used happily in conjunction with existing ANSI libraries +such as Termcolor: + +.. code-block:: python + + from colorama import init + from termcolor import colored + + # use Colorama to make Termcolor work on Windows too + init() + + # then use Termcolor for all colored text output + print(colored('Hello, World!', 'green', 'on_red')) + +Available formatting constants are:: + + Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Style: DIM, NORMAL, BRIGHT, RESET_ALL + +``Style.RESET_ALL`` resets foreground, background, and brightness. Colorama will +perform this reset automatically on program exit. + + +Cursor Positioning +------------------ + +ANSI codes to reposition the cursor are supported. See ``demos/demo06.py`` for +an example of how to generate them. + + +Init Keyword Args +----------------- + +``init()`` accepts some ``**kwargs`` to override default behaviour. + +init(autoreset=False): + If you find yourself repeatedly sending reset sequences to turn off color + changes at the end of every print, then ``init(autoreset=True)`` will + automate that: + + .. code-block:: python + + from colorama import init + init(autoreset=True) + print(Fore.RED + 'some red text') + print('automatically back to default color again') + +init(strip=None): + Pass ``True`` or ``False`` to override whether ansi codes should be + stripped from the output. The default behaviour is to strip if on Windows + or if output is redirected (not a tty). + +init(convert=None): + Pass ``True`` or ``False`` to override whether to convert ANSI codes in the + output into win32 calls. The default behaviour is to convert if on Windows + and output is to a tty (terminal). + +init(wrap=True): + On Windows, colorama works by replacing ``sys.stdout`` and ``sys.stderr`` + with proxy objects, which override the ``.write()`` method to do their work. + If this wrapping causes you problems, then this can be disabled by passing + ``init(wrap=False)``. The default behaviour is to wrap if ``autoreset`` or + ``strip`` or ``convert`` are True. + + When wrapping is disabled, colored printing on non-Windows platforms will + continue to work as normal. To do cross-platform colored output, you can + use Colorama's ``AnsiToWin32`` proxy directly: + + .. code-block:: python + + import sys + from colorama import init, AnsiToWin32 + init(wrap=False) + stream = AnsiToWin32(sys.stderr).stream + + # Python 2 + print >>stream, Fore.BLUE + 'blue text on stderr' + + # Python 3 + print(Fore.BLUE + 'blue text on stderr', file=stream) + + +Installation +======================= +colorama is currently installable from PyPI: + + pip install colorama + +colorama also can be installed by the conda package manager: + + conda install -c anaconda colorama + + +Status & Known Problems +======================= + +I've personally only tested it on Windows XP (CMD, Console2), Ubuntu +(gnome-terminal, xterm), and OS X. + +Some presumably valid ANSI sequences aren't recognised (see details below), +but to my knowledge nobody has yet complained about this. Puzzling. + +See outstanding issues and wishlist: +https://github.com/tartley/colorama/issues + +If anything doesn't work for you, or doesn't do what you expected or hoped for, +I'd love to hear about it on that issues list, would be delighted by patches, +and would be happy to grant commit access to anyone who submits a working patch +or two. + + +Recognised ANSI Sequences +========================= + +ANSI sequences generally take the form: + + ESC [ ; ... + +Where ```` is an integer, and ```` is a single letter. Zero or +more params are passed to a ````. If no params are passed, it is +generally synonymous with passing a single zero. No spaces exist in the +sequence; they have been inserted here simply to read more easily. + +The only ANSI sequences that colorama converts into win32 calls are:: + + ESC [ 0 m # reset all (colors and brightness) + ESC [ 1 m # bright + ESC [ 2 m # dim (looks same as normal brightness) + ESC [ 22 m # normal brightness + + # FOREGROUND: + ESC [ 30 m # black + ESC [ 31 m # red + ESC [ 32 m # green + ESC [ 33 m # yellow + ESC [ 34 m # blue + ESC [ 35 m # magenta + ESC [ 36 m # cyan + ESC [ 37 m # white + ESC [ 39 m # reset + + # BACKGROUND + ESC [ 40 m # black + ESC [ 41 m # red + ESC [ 42 m # green + ESC [ 43 m # yellow + ESC [ 44 m # blue + ESC [ 45 m # magenta + ESC [ 46 m # cyan + ESC [ 47 m # white + ESC [ 49 m # reset + + # cursor positioning + ESC [ y;x H # position cursor at x across, y down + ESC [ y;x f # position cursor at x across, y down + ESC [ n A # move cursor n lines up + ESC [ n B # move cursor n lines down + ESC [ n C # move cursor n characters forward + ESC [ n D # move cursor n characters backward + + # clear the screen + ESC [ mode J # clear the screen + + # clear the line + ESC [ mode K # clear the line + +Multiple numeric params to the ``'m'`` command can be combined into a single +sequence:: + + ESC [ 36 ; 45 ; 1 m # bright cyan text on magenta background + +All other ANSI sequences of the form ``ESC [ ; ... `` +are silently stripped from the output on Windows. + +Any other form of ANSI sequence, such as single-character codes or alternative +initial characters, are not recognised or stripped. It would be cool to add +them though. Let me know if it would be useful for you, via the Issues on +GitHub. + + +Development +=========== + +Help and fixes welcome! + +Running tests requires: + +- Michael Foord's ``mock`` module to be installed. +- Tests are written using 2010-era updates to ``unittest`` + +To run tests:: + + python -m unittest discover -p *_test.py + +This, like a few other handy commands, is captured in a ``Makefile``. + +If you use nose to run the tests, you must pass the ``-s`` flag; otherwise, +``nosetests`` applies its own proxy to ``stdout``, which confuses the unit +tests. + + +Professional support +==================== + +.. |tideliftlogo| image:: https://cdn2.hubspot.net/hubfs/4008838/website/logos/logos_for_download/Tidelift_primary-shorthand-logo.png + :alt: Tidelift + :target: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme + +.. list-table:: + :widths: 10 100 + + * - |tideliftlogo| + - Professional support for colorama is available as part of the + `Tidelift Subscription`_. + Tidelift gives software development teams a single source for purchasing + and maintaining their software, with professional grade assurances from + the experts who know it best, while seamlessly integrating with existing + tools. + +.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme + + +Thanks +====== +* Marc Schlaich (schlamar) for a ``setup.py`` fix for Python2.5. +* Marc Abramowitz, reported & fixed a crash on exit with closed ``stdout``, + providing a solution to issue #7's setuptools/distutils debate, + and other fixes. +* User 'eryksun', for guidance on correctly instantiating ``ctypes.windll``. +* Matthew McCormick for politely pointing out a longstanding crash on non-Win. +* Ben Hoyt, for a magnificent fix under 64-bit Windows. +* Jesse at Empty Square for submitting a fix for examples in the README. +* User 'jamessp', an observant documentation fix for cursor positioning. +* User 'vaal1239', Dave Mckee & Lackner Kristof for a tiny but much-needed Win7 + fix. +* Julien Stuyck, for wisely suggesting Python3 compatible updates to README. +* Daniel Griffith for multiple fabulous patches. +* Oscar Lesta for a valuable fix to stop ANSI chars being sent to non-tty + output. +* Roger Binns, for many suggestions, valuable feedback, & bug reports. +* Tim Golden for thought and much appreciated feedback on the initial idea. +* User 'Zearin' for updates to the README file. +* John Szakmeister for adding support for light colors +* Charles Merriam for adding documentation to demos +* Jurko for a fix on 64-bit Windows CPython2.5 w/o ctypes +* Florian Bruhin for a fix when stdout or stderr are None +* Thomas Weininger for fixing ValueError on Windows +* Remi Rampin for better Github integration and fixes to the README file +* Simeon Visser for closing a file handle using 'with' and updating classifiers + to include Python 3.3 and 3.4 +* Andy Neff for fixing RESET of LIGHT_EX colors. +* Jonathan Hartley for the initial idea and implementation. + + diff --git a/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/RECORD b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..4755048355684c5efdbe55aae4f0dbdaa9b19d94 --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/RECORD @@ -0,0 +1,22 @@ +colorama/__init__.py,sha256=DqjXH9URVP3IJwmMt7peYw50ns1RNAymIB9-XdPEFV8,239 +colorama/ansi.py,sha256=Fi0un-QLqRm-v7o_nKiOqyC8PapBJK7DLV_q9LKtTO0,2524 +colorama/ansitowin32.py,sha256=u8QaqdqS_xYSfNkPM1eRJLHz6JMWPodaJaP0mxgHCDc,10462 +colorama/initialise.py,sha256=PprovDNxMTrvoNHFcL2NZjpH2XzDc8BLxLxiErfUl4k,1915 +colorama/win32.py,sha256=bJ8Il9jwaBN5BJ8bmN6FoYZ1QYuMKv2j8fGrXh7TJjw,5404 +colorama/winterm.py,sha256=2y_2b7Zsv34feAsP67mLOVc-Bgq51mdYGo571VprlrM,6438 +colorama-0.4.3.dist-info/AUTHORS.txt,sha256=RtqU9KfonVGhI48DAA4-yTOBUhBtQTjFhaDzHoyh7uU,21518 +colorama-0.4.3.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090 +colorama-0.4.3.dist-info/METADATA,sha256=-ovqULHfBHs9pV2e_Ua8-w2VSVLno-6x36SXSTsWvSc,14432 +colorama-0.4.3.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +colorama-0.4.3.dist-info/top_level.txt,sha256=_Kx6-Cni2BT1PEATPhrSRxo0d7kSgfBbHf5o7IF1ABw,9 +colorama-0.4.3.dist-info/RECORD,, +colorama/winterm.cpython-38.pyc,, +colorama/initialise.cpython-38.pyc,, +colorama/win32.cpython-38.pyc,, +colorama/ansi.cpython-38.pyc,, +colorama-0.4.3.virtualenv,, +colorama/__init__.cpython-38.pyc,, +colorama/ansitowin32.cpython-38.pyc,, +colorama/__pycache__,, +colorama-0.4.3.dist-info/INSTALLER,, +colorama-0.4.3.dist-info/__pycache__,, \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/WHEEL b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ef99c6cf3283b50a273ac4c6d009a0aa85597070 --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..3fcfb51b2ad06cc0cb6c7155260a79fd2896b3d1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama-0.4.3.dist-info/top_level.txt @@ -0,0 +1 @@ +colorama diff --git a/robot/lib/python3.8/site-packages/colorama-0.4.3.virtualenv b/robot/lib/python3.8/site-packages/colorama-0.4.3.virtualenv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/colorama/__init__.py b/robot/lib/python3.8/site-packages/colorama/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..34c263cc8bb4a12d99b9d375193d4eb634ed094a --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama/__init__.py @@ -0,0 +1,6 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from .initialise import init, deinit, reinit, colorama_text +from .ansi import Fore, Back, Style, Cursor +from .ansitowin32 import AnsiToWin32 + +__version__ = '0.4.3' diff --git a/robot/lib/python3.8/site-packages/colorama/ansi.py b/robot/lib/python3.8/site-packages/colorama/ansi.py new file mode 100644 index 0000000000000000000000000000000000000000..78776588db9410924d8e4af0922fbc3960a37624 --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama/ansi.py @@ -0,0 +1,102 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +''' +This module generates ANSI character codes to printing colors to terminals. +See: http://en.wikipedia.org/wiki/ANSI_escape_code +''' + +CSI = '\033[' +OSC = '\033]' +BEL = '\007' + + +def code_to_chars(code): + return CSI + str(code) + 'm' + +def set_title(title): + return OSC + '2;' + title + BEL + +def clear_screen(mode=2): + return CSI + str(mode) + 'J' + +def clear_line(mode=2): + return CSI + str(mode) + 'K' + + +class AnsiCodes(object): + def __init__(self): + # the subclasses declare class attributes which are numbers. + # Upon instantiation we define instance attributes, which are the same + # as the class attributes but wrapped with the ANSI escape sequence + for name in dir(self): + if not name.startswith('_'): + value = getattr(self, name) + setattr(self, name, code_to_chars(value)) + + +class AnsiCursor(object): + def UP(self, n=1): + return CSI + str(n) + 'A' + def DOWN(self, n=1): + return CSI + str(n) + 'B' + def FORWARD(self, n=1): + return CSI + str(n) + 'C' + def BACK(self, n=1): + return CSI + str(n) + 'D' + def POS(self, x=1, y=1): + return CSI + str(y) + ';' + str(x) + 'H' + + +class AnsiFore(AnsiCodes): + BLACK = 30 + RED = 31 + GREEN = 32 + YELLOW = 33 + BLUE = 34 + MAGENTA = 35 + CYAN = 36 + WHITE = 37 + RESET = 39 + + # These are fairly well supported, but not part of the standard. + LIGHTBLACK_EX = 90 + LIGHTRED_EX = 91 + LIGHTGREEN_EX = 92 + LIGHTYELLOW_EX = 93 + LIGHTBLUE_EX = 94 + LIGHTMAGENTA_EX = 95 + LIGHTCYAN_EX = 96 + LIGHTWHITE_EX = 97 + + +class AnsiBack(AnsiCodes): + BLACK = 40 + RED = 41 + GREEN = 42 + YELLOW = 43 + BLUE = 44 + MAGENTA = 45 + CYAN = 46 + WHITE = 47 + RESET = 49 + + # These are fairly well supported, but not part of the standard. + LIGHTBLACK_EX = 100 + LIGHTRED_EX = 101 + LIGHTGREEN_EX = 102 + LIGHTYELLOW_EX = 103 + LIGHTBLUE_EX = 104 + LIGHTMAGENTA_EX = 105 + LIGHTCYAN_EX = 106 + LIGHTWHITE_EX = 107 + + +class AnsiStyle(AnsiCodes): + BRIGHT = 1 + DIM = 2 + NORMAL = 22 + RESET_ALL = 0 + +Fore = AnsiFore() +Back = AnsiBack() +Style = AnsiStyle() +Cursor = AnsiCursor() diff --git a/robot/lib/python3.8/site-packages/colorama/ansitowin32.py b/robot/lib/python3.8/site-packages/colorama/ansitowin32.py new file mode 100644 index 0000000000000000000000000000000000000000..359c92be50ee3c97aafbc8d31c16e5fbf2e6d022 --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama/ansitowin32.py @@ -0,0 +1,257 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import re +import sys +import os + +from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style +from .winterm import WinTerm, WinColor, WinStyle +from .win32 import windll, winapi_test + + +winterm = None +if windll is not None: + winterm = WinTerm() + + +class StreamWrapper(object): + ''' + Wraps a stream (such as stdout), acting as a transparent proxy for all + attribute access apart from method 'write()', which is delegated to our + Converter instance. + ''' + def __init__(self, wrapped, converter): + # double-underscore everything to prevent clashes with names of + # attributes on the wrapped stream object. + self.__wrapped = wrapped + self.__convertor = converter + + def __getattr__(self, name): + return getattr(self.__wrapped, name) + + def __enter__(self, *args, **kwargs): + # special method lookup bypasses __getattr__/__getattribute__, see + # https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit + # thus, contextlib magic methods are not proxied via __getattr__ + return self.__wrapped.__enter__(*args, **kwargs) + + def __exit__(self, *args, **kwargs): + return self.__wrapped.__exit__(*args, **kwargs) + + def write(self, text): + self.__convertor.write(text) + + def isatty(self): + stream = self.__wrapped + if 'PYCHARM_HOSTED' in os.environ: + if stream is not None and (stream is sys.__stdout__ or stream is sys.__stderr__): + return True + try: + stream_isatty = stream.isatty + except AttributeError: + return False + else: + return stream_isatty() + + @property + def closed(self): + stream = self.__wrapped + try: + return stream.closed + except AttributeError: + return True + + +class AnsiToWin32(object): + ''' + Implements a 'write()' method which, on Windows, will strip ANSI character + sequences from the text, and if outputting to a tty, will convert them into + win32 function calls. + ''' + ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer + ANSI_OSC_RE = re.compile('\001?\033\\]((?:.|;)*?)(\x07)\002?') # Operating System Command + + def __init__(self, wrapped, convert=None, strip=None, autoreset=False): + # The wrapped stream (normally sys.stdout or sys.stderr) + self.wrapped = wrapped + + # should we reset colors to defaults after every .write() + self.autoreset = autoreset + + # create the proxy wrapping our output stream + self.stream = StreamWrapper(wrapped, self) + + on_windows = os.name == 'nt' + # We test if the WinAPI works, because even if we are on Windows + # we may be using a terminal that doesn't support the WinAPI + # (e.g. Cygwin Terminal). In this case it's up to the terminal + # to support the ANSI codes. + conversion_supported = on_windows and winapi_test() + + # should we strip ANSI sequences from our output? + if strip is None: + strip = conversion_supported or (not self.stream.closed and not self.stream.isatty()) + self.strip = strip + + # should we should convert ANSI sequences into win32 calls? + if convert is None: + convert = conversion_supported and not self.stream.closed and self.stream.isatty() + self.convert = convert + + # dict of ansi codes to win32 functions and parameters + self.win32_calls = self.get_win32_calls() + + # are we wrapping stderr? + self.on_stderr = self.wrapped is sys.stderr + + def should_wrap(self): + ''' + True if this class is actually needed. If false, then the output + stream will not be affected, nor will win32 calls be issued, so + wrapping stdout is not actually required. This will generally be + False on non-Windows platforms, unless optional functionality like + autoreset has been requested using kwargs to init() + ''' + return self.convert or self.strip or self.autoreset + + def get_win32_calls(self): + if self.convert and winterm: + return { + AnsiStyle.RESET_ALL: (winterm.reset_all, ), + AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT), + AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL), + AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL), + AnsiFore.BLACK: (winterm.fore, WinColor.BLACK), + AnsiFore.RED: (winterm.fore, WinColor.RED), + AnsiFore.GREEN: (winterm.fore, WinColor.GREEN), + AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW), + AnsiFore.BLUE: (winterm.fore, WinColor.BLUE), + AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA), + AnsiFore.CYAN: (winterm.fore, WinColor.CYAN), + AnsiFore.WHITE: (winterm.fore, WinColor.GREY), + AnsiFore.RESET: (winterm.fore, ), + AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True), + AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True), + AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True), + AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True), + AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True), + AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True), + AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True), + AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True), + AnsiBack.BLACK: (winterm.back, WinColor.BLACK), + AnsiBack.RED: (winterm.back, WinColor.RED), + AnsiBack.GREEN: (winterm.back, WinColor.GREEN), + AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW), + AnsiBack.BLUE: (winterm.back, WinColor.BLUE), + AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA), + AnsiBack.CYAN: (winterm.back, WinColor.CYAN), + AnsiBack.WHITE: (winterm.back, WinColor.GREY), + AnsiBack.RESET: (winterm.back, ), + AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True), + AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True), + AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True), + AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True), + AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True), + AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True), + AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True), + AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True), + } + return dict() + + def write(self, text): + if self.strip or self.convert: + self.write_and_convert(text) + else: + self.wrapped.write(text) + self.wrapped.flush() + if self.autoreset: + self.reset_all() + + + def reset_all(self): + if self.convert: + self.call_win32('m', (0,)) + elif not self.strip and not self.stream.closed: + self.wrapped.write(Style.RESET_ALL) + + + def write_and_convert(self, text): + ''' + Write the given text to our wrapped stream, stripping any ANSI + sequences from the text, and optionally converting them into win32 + calls. + ''' + cursor = 0 + text = self.convert_osc(text) + for match in self.ANSI_CSI_RE.finditer(text): + start, end = match.span() + self.write_plain_text(text, cursor, start) + self.convert_ansi(*match.groups()) + cursor = end + self.write_plain_text(text, cursor, len(text)) + + + def write_plain_text(self, text, start, end): + if start < end: + self.wrapped.write(text[start:end]) + self.wrapped.flush() + + + def convert_ansi(self, paramstring, command): + if self.convert: + params = self.extract_params(command, paramstring) + self.call_win32(command, params) + + + def extract_params(self, command, paramstring): + if command in 'Hf': + params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';')) + while len(params) < 2: + # defaults: + params = params + (1,) + else: + params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0) + if len(params) == 0: + # defaults: + if command in 'JKm': + params = (0,) + elif command in 'ABCD': + params = (1,) + + return params + + + def call_win32(self, command, params): + if command == 'm': + for param in params: + if param in self.win32_calls: + func_args = self.win32_calls[param] + func = func_args[0] + args = func_args[1:] + kwargs = dict(on_stderr=self.on_stderr) + func(*args, **kwargs) + elif command in 'J': + winterm.erase_screen(params[0], on_stderr=self.on_stderr) + elif command in 'K': + winterm.erase_line(params[0], on_stderr=self.on_stderr) + elif command in 'Hf': # cursor position - absolute + winterm.set_cursor_position(params, on_stderr=self.on_stderr) + elif command in 'ABCD': # cursor position - relative + n = params[0] + # A - up, B - down, C - forward, D - back + x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command] + winterm.cursor_adjust(x, y, on_stderr=self.on_stderr) + + + def convert_osc(self, text): + for match in self.ANSI_OSC_RE.finditer(text): + start, end = match.span() + text = text[:start] + text[end:] + paramstring, command = match.groups() + if command in '\x07': # \x07 = BEL + params = paramstring.split(";") + # 0 - change title and icon (we will only change title) + # 1 - change icon (we don't support this) + # 2 - change title + if params[0] in '02': + winterm.set_title(params[1]) + return text diff --git a/robot/lib/python3.8/site-packages/colorama/initialise.py b/robot/lib/python3.8/site-packages/colorama/initialise.py new file mode 100644 index 0000000000000000000000000000000000000000..430d0668727cd0521270a52c962867907d743b34 --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama/initialise.py @@ -0,0 +1,80 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import atexit +import contextlib +import sys + +from .ansitowin32 import AnsiToWin32 + + +orig_stdout = None +orig_stderr = None + +wrapped_stdout = None +wrapped_stderr = None + +atexit_done = False + + +def reset_all(): + if AnsiToWin32 is not None: # Issue #74: objects might become None at exit + AnsiToWin32(orig_stdout).reset_all() + + +def init(autoreset=False, convert=None, strip=None, wrap=True): + + if not wrap and any([autoreset, convert, strip]): + raise ValueError('wrap=False conflicts with any other arg=True') + + global wrapped_stdout, wrapped_stderr + global orig_stdout, orig_stderr + + orig_stdout = sys.stdout + orig_stderr = sys.stderr + + if sys.stdout is None: + wrapped_stdout = None + else: + sys.stdout = wrapped_stdout = \ + wrap_stream(orig_stdout, convert, strip, autoreset, wrap) + if sys.stderr is None: + wrapped_stderr = None + else: + sys.stderr = wrapped_stderr = \ + wrap_stream(orig_stderr, convert, strip, autoreset, wrap) + + global atexit_done + if not atexit_done: + atexit.register(reset_all) + atexit_done = True + + +def deinit(): + if orig_stdout is not None: + sys.stdout = orig_stdout + if orig_stderr is not None: + sys.stderr = orig_stderr + + +@contextlib.contextmanager +def colorama_text(*args, **kwargs): + init(*args, **kwargs) + try: + yield + finally: + deinit() + + +def reinit(): + if wrapped_stdout is not None: + sys.stdout = wrapped_stdout + if wrapped_stderr is not None: + sys.stderr = wrapped_stderr + + +def wrap_stream(stream, convert, strip, autoreset, wrap): + if wrap: + wrapper = AnsiToWin32(stream, + convert=convert, strip=strip, autoreset=autoreset) + if wrapper.should_wrap(): + stream = wrapper.stream + return stream diff --git a/robot/lib/python3.8/site-packages/colorama/win32.py b/robot/lib/python3.8/site-packages/colorama/win32.py new file mode 100644 index 0000000000000000000000000000000000000000..c2d836033673993e00a02d5a3802b61cd051cf08 --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama/win32.py @@ -0,0 +1,152 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. + +# from winbase.h +STDOUT = -11 +STDERR = -12 + +try: + import ctypes + from ctypes import LibraryLoader + windll = LibraryLoader(ctypes.WinDLL) + from ctypes import wintypes +except (AttributeError, ImportError): + windll = None + SetConsoleTextAttribute = lambda *_: None + winapi_test = lambda *_: None +else: + from ctypes import byref, Structure, c_char, POINTER + + COORD = wintypes._COORD + + class CONSOLE_SCREEN_BUFFER_INFO(Structure): + """struct in wincon.h.""" + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", wintypes.WORD), + ("srWindow", wintypes.SMALL_RECT), + ("dwMaximumWindowSize", COORD), + ] + def __str__(self): + return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( + self.dwSize.Y, self.dwSize.X + , self.dwCursorPosition.Y, self.dwCursorPosition.X + , self.wAttributes + , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right + , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X + ) + + _GetStdHandle = windll.kernel32.GetStdHandle + _GetStdHandle.argtypes = [ + wintypes.DWORD, + ] + _GetStdHandle.restype = wintypes.HANDLE + + _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo + _GetConsoleScreenBufferInfo.argtypes = [ + wintypes.HANDLE, + POINTER(CONSOLE_SCREEN_BUFFER_INFO), + ] + _GetConsoleScreenBufferInfo.restype = wintypes.BOOL + + _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute + _SetConsoleTextAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + ] + _SetConsoleTextAttribute.restype = wintypes.BOOL + + _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition + _SetConsoleCursorPosition.argtypes = [ + wintypes.HANDLE, + COORD, + ] + _SetConsoleCursorPosition.restype = wintypes.BOOL + + _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA + _FillConsoleOutputCharacterA.argtypes = [ + wintypes.HANDLE, + c_char, + wintypes.DWORD, + COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputCharacterA.restype = wintypes.BOOL + + _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute + _FillConsoleOutputAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + wintypes.DWORD, + COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputAttribute.restype = wintypes.BOOL + + _SetConsoleTitleW = windll.kernel32.SetConsoleTitleW + _SetConsoleTitleW.argtypes = [ + wintypes.LPCWSTR + ] + _SetConsoleTitleW.restype = wintypes.BOOL + + def _winapi_test(handle): + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = _GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return bool(success) + + def winapi_test(): + return any(_winapi_test(h) for h in + (_GetStdHandle(STDOUT), _GetStdHandle(STDERR))) + + def GetConsoleScreenBufferInfo(stream_id=STDOUT): + handle = _GetStdHandle(stream_id) + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = _GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return csbi + + def SetConsoleTextAttribute(stream_id, attrs): + handle = _GetStdHandle(stream_id) + return _SetConsoleTextAttribute(handle, attrs) + + def SetConsoleCursorPosition(stream_id, position, adjust=True): + position = COORD(*position) + # If the position is out of range, do nothing. + if position.Y <= 0 or position.X <= 0: + return + # Adjust for Windows' SetConsoleCursorPosition: + # 1. being 0-based, while ANSI is 1-based. + # 2. expecting (x,y), while ANSI uses (y,x). + adjusted_position = COORD(position.Y - 1, position.X - 1) + if adjust: + # Adjust for viewport's scroll position + sr = GetConsoleScreenBufferInfo(STDOUT).srWindow + adjusted_position.Y += sr.Top + adjusted_position.X += sr.Left + # Resume normal processing + handle = _GetStdHandle(stream_id) + return _SetConsoleCursorPosition(handle, adjusted_position) + + def FillConsoleOutputCharacter(stream_id, char, length, start): + handle = _GetStdHandle(stream_id) + char = c_char(char.encode()) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + success = _FillConsoleOutputCharacterA( + handle, char, length, start, byref(num_written)) + return num_written.value + + def FillConsoleOutputAttribute(stream_id, attr, length, start): + ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' + handle = _GetStdHandle(stream_id) + attribute = wintypes.WORD(attr) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + return _FillConsoleOutputAttribute( + handle, attribute, length, start, byref(num_written)) + + def SetConsoleTitle(title): + return _SetConsoleTitleW(title) diff --git a/robot/lib/python3.8/site-packages/colorama/winterm.py b/robot/lib/python3.8/site-packages/colorama/winterm.py new file mode 100644 index 0000000000000000000000000000000000000000..0fdb4ec4e91090876dc3fbf207049b521fa0dd73 --- /dev/null +++ b/robot/lib/python3.8/site-packages/colorama/winterm.py @@ -0,0 +1,169 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from . import win32 + + +# from wincon.h +class WinColor(object): + BLACK = 0 + BLUE = 1 + GREEN = 2 + CYAN = 3 + RED = 4 + MAGENTA = 5 + YELLOW = 6 + GREY = 7 + +# from wincon.h +class WinStyle(object): + NORMAL = 0x00 # dim text, dim background + BRIGHT = 0x08 # bright text, dim background + BRIGHT_BACKGROUND = 0x80 # dim text, bright background + +class WinTerm(object): + + def __init__(self): + self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes + self.set_attrs(self._default) + self._default_fore = self._fore + self._default_back = self._back + self._default_style = self._style + # In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style. + # So that LIGHT_EX colors and BRIGHT style do not clobber each other, + # we track them separately, since LIGHT_EX is overwritten by Fore/Back + # and BRIGHT is overwritten by Style codes. + self._light = 0 + + def get_attrs(self): + return self._fore + self._back * 16 + (self._style | self._light) + + def set_attrs(self, value): + self._fore = value & 7 + self._back = (value >> 4) & 7 + self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND) + + def reset_all(self, on_stderr=None): + self.set_attrs(self._default) + self.set_console(attrs=self._default) + self._light = 0 + + def fore(self, fore=None, light=False, on_stderr=False): + if fore is None: + fore = self._default_fore + self._fore = fore + # Emulate LIGHT_EX with BRIGHT Style + if light: + self._light |= WinStyle.BRIGHT + else: + self._light &= ~WinStyle.BRIGHT + self.set_console(on_stderr=on_stderr) + + def back(self, back=None, light=False, on_stderr=False): + if back is None: + back = self._default_back + self._back = back + # Emulate LIGHT_EX with BRIGHT_BACKGROUND Style + if light: + self._light |= WinStyle.BRIGHT_BACKGROUND + else: + self._light &= ~WinStyle.BRIGHT_BACKGROUND + self.set_console(on_stderr=on_stderr) + + def style(self, style=None, on_stderr=False): + if style is None: + style = self._default_style + self._style = style + self.set_console(on_stderr=on_stderr) + + def set_console(self, attrs=None, on_stderr=False): + if attrs is None: + attrs = self.get_attrs() + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleTextAttribute(handle, attrs) + + def get_position(self, handle): + position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition + # Because Windows coordinates are 0-based, + # and win32.SetConsoleCursorPosition expects 1-based. + position.X += 1 + position.Y += 1 + return position + + def set_cursor_position(self, position=None, on_stderr=False): + if position is None: + # I'm not currently tracking the position, so there is no default. + # position = self.get_position() + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleCursorPosition(handle, position) + + def cursor_adjust(self, x, y, on_stderr=False): + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + position = self.get_position(handle) + adjusted_position = (position.Y + y, position.X + x) + win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False) + + def erase_screen(self, mode=0, on_stderr=False): + # 0 should clear from the cursor to the end of the screen. + # 1 should clear from the cursor to the beginning of the screen. + # 2 should clear the entire screen, and move cursor to (1,1) + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + csbi = win32.GetConsoleScreenBufferInfo(handle) + # get the number of character cells in the current buffer + cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y + # get number of character cells before current cursor position + cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X + if mode == 0: + from_coord = csbi.dwCursorPosition + cells_to_erase = cells_in_screen - cells_before_cursor + elif mode == 1: + from_coord = win32.COORD(0, 0) + cells_to_erase = cells_before_cursor + elif mode == 2: + from_coord = win32.COORD(0, 0) + cells_to_erase = cells_in_screen + else: + # invalid mode + return + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) + if mode == 2: + # put the cursor where needed + win32.SetConsoleCursorPosition(handle, (1, 1)) + + def erase_line(self, mode=0, on_stderr=False): + # 0 should clear from the cursor to the end of the line. + # 1 should clear from the cursor to the beginning of the line. + # 2 should clear the entire line. + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + csbi = win32.GetConsoleScreenBufferInfo(handle) + if mode == 0: + from_coord = csbi.dwCursorPosition + cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X + elif mode == 1: + from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) + cells_to_erase = csbi.dwCursorPosition.X + elif mode == 2: + from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) + cells_to_erase = csbi.dwSize.X + else: + # invalid mode + return + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) + + def set_title(self, title): + win32.SetConsoleTitle(title) diff --git a/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/AUTHORS.txt b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/AUTHORS.txt new file mode 100644 index 0000000000000000000000000000000000000000..72c87d7d38ae7bf859717c333a5ee8230f6ce624 --- /dev/null +++ b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/AUTHORS.txt @@ -0,0 +1,562 @@ +A_Rog +Aakanksha Agrawal <11389424+rasponic@users.noreply.github.com> +Abhinav Sagar <40603139+abhinavsagar@users.noreply.github.com> +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Tse +Adam Wentz +admin +Adrien Morison +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Aleks Bunin +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Alexey Popravka +Alli +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrey Bulgakov +Andrés Delfino <34587441+andresdelfino@users.noreply.github.com> +Andrés Delfino +Andy Freeland +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anuj Godase +AQNOUCH Mohammed +AraHaan +Arindam Choudhury +Armin Ronacher +Artem +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avner Cohen +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Darnell +Ben Hoyt +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Brandon L. Reiss +Brandt Bucher +Brett Randall +Brian Cristante <33549821+brcrista@users.noreply.github.com> +Brian Cristante +Brian Rosner +BrownTruck +Bruno Oliveira +Bruno Renié +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +c22 +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Chih-Hsuan Yen +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris McDonough +Chris Wolfe +Christian Heimes +Christian Oudard +Christopher Hunt +Christopher Snyder +Clark Boylan +Clay McClure +Cody +Cody Soyland +Colin Watson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Curtis Doty +cytolentino +Damian Quiroga +Dan Black +Dan Savilonis +Dan Sully +daniel +Daniel Collins +Daniel Hahler +Daniel Holth +Daniel Jost +Daniel Shaulov +Daniele Esposti +Daniele Procida +Danny Hermes +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Bordeynik +David Caro +David Evans +David Linke +David Pursehouse +David Tucker +David Wales +Davidovich +derwolfe +Desetude +Diego Caraballo +DiegoCaraballo +Dmitry Gladkov +Domen Kožar +Donald Stufft +Dongweiming +Douglas Thor +DrFeathers +Dustin Ingram +Dwayne Bailey +Ed Morley <501702+edmorley@users.noreply.github.com> +Ed Morley +Eitan Adler +ekristina +elainechan +Eli Schwartz +Eli Schwartz +Emil Burzo +Emil Styrke +Endoh Takanao +enoch +Erdinc Mutlu +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Ernest W Durbin III +Ernest W. Durbin III +Erwin Janssen +Eugene Vereshchagin +everdimension +Felix Yan +fiber-space +Filip Kokosiński +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gdanielson +Geoffrey Lehée +Geoffrey Sneddon +George Song +Georgi Valkov +Giftlin Rajaiah +gizmoguy1 +gkdoc <40815324+gkdoc@users.noreply.github.com> +Gopinath M <31352222+mgopi1990@users.noreply.github.com> +GOTO Hayato <3532528+gh640@users.noreply.github.com> +gpiks +Guilherme Espada +Guy Rozendorn +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +Herbert Pfennig +Hsiaoming Yang +Hugo +Hugo Lopes Tavares +Hugo van Kemenade +hugovk +Hynek Schlawack +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ilya Baryshev +INADA Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Ivan Pozdeev +Jacob Kim +jakirkham +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James Cleveland +James Cleveland +James Firth +James Polley +Jan Pokorný +Jannis Leidel +jarondl +Jason R. Coombs +Jay Graves +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jelmer Vernooij +jenix21 +Jeremy Stanley +Jeremy Zafran +Jiashuo Li +Jim Garrison +Jivan Amara +John Paton +John-Scott Atlakson +johnthagen +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joost Molenaar +Jorge Niedbalski +Joseph Long +Josh Bronson +Josh Hansen +Josh Schneier +Juanjo Bazán +Julian Berman +Julian Gethmann +Julien Demoor +jwg4 +Jyrki Pulliainen +Kai Chen +Kamal Bin Mustafa +kaustav haldar +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kexuan Sun +Kit Randel +kpinc +Krishna Oza +Kumar McMillan +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurie Opperman +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +Loren Carvalho +Lucas Cimon +Ludovic Gasc +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +László Kiss Kollár +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark Williams +Mark Williams +Markus Hametner +Masaki +Masklinn +Matej Stuchlik +Mathew Jennings +Mathieu Bridon +Matt Good +Matt Maker +Matt Robenolt +matthew +Matthew Einhorn +Matthew Gilliard +Matthew Iversen +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna <44498973+mbaluna@users.noreply.github.com> +mdebi <17590103+mdebi@users.noreply.github.com> +memoselyk +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Williamson +michaelpacer +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Monica Baluna +montefra +Monty Taylor +Nate Coraor +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nikhil Benesch +Nitesh Sharma +Nowell Strite +NtaleGrey +nvdv +Ofekmeister +ofrinevo +Oliver Jeeves +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +OMOTO Kenji +Omry Yadan +Oren Held +Oscar Benjamin +Oz N Tiram +Pachwenko <32424503+Pachwenko@users.noreply.github.com> +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavithra Eswaramoorthy <33131404+QueenCoffee@users.noreply.github.com> +Pawel Jasinski +Pekka Klärck +Peter Lisák +Peter Waller +petr-tik +Phaneendra Chiruvella +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Pratik Mallya +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal <7895pulkit@gmail.com> +Qiangning Hong +Quentin Pradet +R. David Murray +Rafael Caricio +Ralf Schmitt +Razzi Abuissa +rdb +Remi Rampin +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Richard Jones +RobberPhex +Robert Collins +Robert McGibbon +Robert T. McGibbon +robin elisha robinson +Roey Berman +Rohan Jain +Rohan Jain +Rohan Jain +Roman Bogorodskiy +Romuald Brunet +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +Roy Wellington Ⅳ +Ryan Wooden +ryneeverett +Sachi King +Salvatore Rinchiera +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sergey Vasilyev +Seth Woodworth +Shlomi Fish +Shovan Maity +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +Sorin Sbarnea +Stavros Korokithakis +Stefan Scherfke +Stephan Erb +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +stonebig +Stéphane Bidoul (ACSONE) +Stéphane Bidoul +Stéphane Klein +Sumana Harihareswara +Sviatoslav Sydorenko +Sviatoslav Sydorenko +Swat009 +Takayuki SHIMIZUKAWA +tbeswick +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tom Forbes +Tom Freudenheim +Tom V +Tomas Orsava +Tomer Chachamu +Tony Beswick +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +Travis Swicegood +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Philippon +Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> +Vitaly Babiy +Vladimir Rutsky +W. Trevor King +Wil Tan +Wilfred Hughes +William ML Leslie +William T Olson +Wilson Mo +wim glenn +Wolfgang Maier +Xavier Fernandez +Xavier Fernandez +xoviat +xtreak +YAMAMOTO Takashi +Yen Chi Hsuan +Yeray Diaz Diaz +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Zearin +Zearin +Zhiping Deng +Zvezdan Petkovic +Łukasz Langa +Семён Марьясин diff --git a/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..737fec5c5352af3d9a6a47a0670da4bdb52c5725 --- /dev/null +++ b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2019 The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/METADATA b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..f4d89c410dc876674e85238d4a455546ab9e9f5f --- /dev/null +++ b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/METADATA @@ -0,0 +1,70 @@ +Metadata-Version: 2.1 +Name: contextlib2 +Version: 0.6.0 +Summary: Backports and enhancements for the contextlib module +Home-page: http://contextlib2.readthedocs.org +Author: Nick Coghlan +Author-email: ncoghlan@gmail.com +License: PSF License +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* + +.. image:: https://jazzband.co/static/img/badge.svg + :target: https://jazzband.co/ + :alt: Jazzband + +.. image:: https://readthedocs.org/projects/contextlib2/badge/?version=latest + :target: https://contextlib2.readthedocs.org/ + :alt: Latest Docs + +.. image:: https://img.shields.io/travis/jazzband/contextlib2/master.svg + :target: http://travis-ci.org/jazzband/contextlib2 + +.. image:: https://coveralls.io/repos/github/jazzband/contextlib2/badge.svg?branch=master + :target: https://coveralls.io/github/jazzband/contextlib2?branch=master + +.. image:: https://landscape.io/github/jazzband/contextlib2/master/landscape.svg + :target: https://landscape.io/github/jazzband/contextlib2/ + +contextlib2 is a backport of the `standard library's contextlib +module `_ to +earlier Python versions. + +It also serves as a real world proving ground for possible future +enhancements to the standard library version. + +Development +----------- + +contextlib2 has no runtime dependencies, but requires ``unittest2`` for testing +on Python 2.x, as well as ``setuptools`` and ``wheel`` to generate universal +wheel archives. + +Local testing is just a matter of running ``python test_contextlib2.py``. + +You can test against multiple versions of Python with +`tox `_:: + + pip install tox + tox + +Versions currently tested in both tox and Travis CI are: + +* CPython 2.7 +* CPython 3.4 +* CPython 3.5 +* CPython 3.6 +* CPython 3.7 +* PyPy +* PyPy3 + + diff --git a/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/RECORD b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..b437135868410f1f9d4cf2f6aa771e5bc2052501 --- /dev/null +++ b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/RECORD @@ -0,0 +1,11 @@ +contextlib2.py,sha256=5HjGflUzwWAUfcILhSmC2GqvoYdZZzFzVfIDztHigUs,16915 +contextlib2-0.6.0.dist-info/AUTHORS.txt,sha256=RtqU9KfonVGhI48DAA4-yTOBUhBtQTjFhaDzHoyh7uU,21518 +contextlib2-0.6.0.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090 +contextlib2-0.6.0.dist-info/METADATA,sha256=gSVIyF9xprVGAJdZdrVoZR9VXpXRWURqyrugGzj66Rk,2291 +contextlib2-0.6.0.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +contextlib2-0.6.0.dist-info/top_level.txt,sha256=RxWWBMkHA_rsw1laXJ8L3yE_fyYaBmvt2bVUvj3WbMg,12 +contextlib2-0.6.0.dist-info/RECORD,, +contextlib2-0.6.0.dist-info/INSTALLER,, +contextlib2-0.6.0.virtualenv,, +contextlib2-0.6.0.dist-info/__pycache__,, +contextlib2.cpython-38.pyc,, \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/WHEEL b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ef99c6cf3283b50a273ac4c6d009a0aa85597070 --- /dev/null +++ b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..03fdf8ed24f99c297b402190021f7c75f205a70f --- /dev/null +++ b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.dist-info/top_level.txt @@ -0,0 +1 @@ +contextlib2 diff --git a/robot/lib/python3.8/site-packages/contextlib2-0.6.0.virtualenv b/robot/lib/python3.8/site-packages/contextlib2-0.6.0.virtualenv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/contextlib2.py b/robot/lib/python3.8/site-packages/contextlib2.py new file mode 100644 index 0000000000000000000000000000000000000000..3aae8f4117cf8824748f5b7bc74b0aca4516bb6a --- /dev/null +++ b/robot/lib/python3.8/site-packages/contextlib2.py @@ -0,0 +1,518 @@ +"""contextlib2 - backports and enhancements to the contextlib module""" + +import abc +import sys +import warnings +from collections import deque +from functools import wraps + +__all__ = ["contextmanager", "closing", "nullcontext", + "AbstractContextManager", + "ContextDecorator", "ExitStack", + "redirect_stdout", "redirect_stderr", "suppress"] + +# Backwards compatibility +__all__ += ["ContextStack"] + + +# Backport abc.ABC +if sys.version_info[:2] >= (3, 4): + _abc_ABC = abc.ABC +else: + _abc_ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()}) + + +# Backport classic class MRO +def _classic_mro(C, result): + if C in result: + return + result.append(C) + for B in C.__bases__: + _classic_mro(B, result) + return result + + +# Backport _collections_abc._check_methods +def _check_methods(C, *methods): + try: + mro = C.__mro__ + except AttributeError: + mro = tuple(_classic_mro(C, [])) + + for method in methods: + for B in mro: + if method in B.__dict__: + if B.__dict__[method] is None: + return NotImplemented + break + else: + return NotImplemented + return True + + +class AbstractContextManager(_abc_ABC): + """An abstract base class for context managers.""" + + def __enter__(self): + """Return `self` upon entering the runtime context.""" + return self + + @abc.abstractmethod + def __exit__(self, exc_type, exc_value, traceback): + """Raise any exception triggered within the runtime context.""" + return None + + @classmethod + def __subclasshook__(cls, C): + """Check whether subclass is considered a subclass of this ABC.""" + if cls is AbstractContextManager: + return _check_methods(C, "__enter__", "__exit__") + return NotImplemented + + +class ContextDecorator(object): + """A base class or mixin that enables context managers to work as decorators.""" + + def refresh_cm(self): + """Returns the context manager used to actually wrap the call to the + decorated function. + + The default implementation just returns *self*. + + Overriding this method allows otherwise one-shot context managers + like _GeneratorContextManager to support use as decorators via + implicit recreation. + + DEPRECATED: refresh_cm was never added to the standard library's + ContextDecorator API + """ + warnings.warn("refresh_cm was never added to the standard library", + DeprecationWarning) + return self._recreate_cm() + + def _recreate_cm(self): + """Return a recreated instance of self. + + Allows an otherwise one-shot context manager like + _GeneratorContextManager to support use as + a decorator via implicit recreation. + + This is a private interface just for _GeneratorContextManager. + See issue #11647 for details. + """ + return self + + def __call__(self, func): + @wraps(func) + def inner(*args, **kwds): + with self._recreate_cm(): + return func(*args, **kwds) + return inner + + +class _GeneratorContextManager(ContextDecorator): + """Helper for @contextmanager decorator.""" + + def __init__(self, func, args, kwds): + self.gen = func(*args, **kwds) + self.func, self.args, self.kwds = func, args, kwds + # Issue 19330: ensure context manager instances have good docstrings + doc = getattr(func, "__doc__", None) + if doc is None: + doc = type(self).__doc__ + self.__doc__ = doc + # Unfortunately, this still doesn't provide good help output when + # inspecting the created context manager instances, since pydoc + # currently bypasses the instance docstring and shows the docstring + # for the class instead. + # See http://bugs.python.org/issue19404 for more details. + + def _recreate_cm(self): + # _GCM instances are one-shot context managers, so the + # CM must be recreated each time a decorated function is + # called + return self.__class__(self.func, self.args, self.kwds) + + def __enter__(self): + try: + return next(self.gen) + except StopIteration: + raise RuntimeError("generator didn't yield") + + def __exit__(self, type, value, traceback): + if type is None: + try: + next(self.gen) + except StopIteration: + return + else: + raise RuntimeError("generator didn't stop") + else: + if value is None: + # Need to force instantiation so we can reliably + # tell if we get the same exception back + value = type() + try: + self.gen.throw(type, value, traceback) + raise RuntimeError("generator didn't stop after throw()") + except StopIteration as exc: + # Suppress StopIteration *unless* it's the same exception that + # was passed to throw(). This prevents a StopIteration + # raised inside the "with" statement from being suppressed. + return exc is not value + except RuntimeError as exc: + # Don't re-raise the passed in exception + if exc is value: + return False + # Likewise, avoid suppressing if a StopIteration exception + # was passed to throw() and later wrapped into a RuntimeError + # (see PEP 479). + if _HAVE_EXCEPTION_CHAINING and exc.__cause__ is value: + return False + raise + except: + # only re-raise if it's *not* the exception that was + # passed to throw(), because __exit__() must not raise + # an exception unless __exit__() itself failed. But throw() + # has to raise the exception to signal propagation, so this + # fixes the impedance mismatch between the throw() protocol + # and the __exit__() protocol. + # + if sys.exc_info()[1] is not value: + raise + + +def contextmanager(func): + """@contextmanager decorator. + + Typical usage: + + @contextmanager + def some_generator(): + + try: + yield + finally: + + + This makes this: + + with some_generator() as : + + + equivalent to this: + + + try: + = + + finally: + + + """ + @wraps(func) + def helper(*args, **kwds): + return _GeneratorContextManager(func, args, kwds) + return helper + + +class closing(object): + """Context to automatically close something at the end of a block. + + Code like this: + + with closing(.open()) as f: + + + is equivalent to this: + + f = .open() + try: + + finally: + f.close() + + """ + def __init__(self, thing): + self.thing = thing + + def __enter__(self): + return self.thing + + def __exit__(self, *exc_info): + self.thing.close() + + +class _RedirectStream(object): + + _stream = None + + def __init__(self, new_target): + self._new_target = new_target + # We use a list of old targets to make this CM re-entrant + self._old_targets = [] + + def __enter__(self): + self._old_targets.append(getattr(sys, self._stream)) + setattr(sys, self._stream, self._new_target) + return self._new_target + + def __exit__(self, exctype, excinst, exctb): + setattr(sys, self._stream, self._old_targets.pop()) + + +class redirect_stdout(_RedirectStream): + """Context manager for temporarily redirecting stdout to another file. + + # How to send help() to stderr + with redirect_stdout(sys.stderr): + help(dir) + + # How to write help() to a file + with open('help.txt', 'w') as f: + with redirect_stdout(f): + help(pow) + """ + + _stream = "stdout" + + +class redirect_stderr(_RedirectStream): + """Context manager for temporarily redirecting stderr to another file.""" + + _stream = "stderr" + + +class suppress(object): + """Context manager to suppress specified exceptions + + After the exception is suppressed, execution proceeds with the next + statement following the with statement. + + with suppress(FileNotFoundError): + os.remove(somefile) + # Execution still resumes here if the file was already removed + """ + + def __init__(self, *exceptions): + self._exceptions = exceptions + + def __enter__(self): + pass + + def __exit__(self, exctype, excinst, exctb): + # Unlike isinstance and issubclass, CPython exception handling + # currently only looks at the concrete type hierarchy (ignoring + # the instance and subclass checking hooks). While Guido considers + # that a bug rather than a feature, it's a fairly hard one to fix + # due to various internal implementation details. suppress provides + # the simpler issubclass based semantics, rather than trying to + # exactly reproduce the limitations of the CPython interpreter. + # + # See http://bugs.python.org/issue12029 for more details + return exctype is not None and issubclass(exctype, self._exceptions) + + +# Context manipulation is Python 3 only +_HAVE_EXCEPTION_CHAINING = sys.version_info[0] >= 3 +if _HAVE_EXCEPTION_CHAINING: + def _make_context_fixer(frame_exc): + def _fix_exception_context(new_exc, old_exc): + # Context may not be correct, so find the end of the chain + while 1: + exc_context = new_exc.__context__ + if exc_context is old_exc: + # Context is already set correctly (see issue 20317) + return + if exc_context is None or exc_context is frame_exc: + break + new_exc = exc_context + # Change the end of the chain to point to the exception + # we expect it to reference + new_exc.__context__ = old_exc + return _fix_exception_context + + def _reraise_with_existing_context(exc_details): + try: + # bare "raise exc_details[1]" replaces our carefully + # set-up context + fixed_ctx = exc_details[1].__context__ + raise exc_details[1] + except BaseException: + exc_details[1].__context__ = fixed_ctx + raise +else: + # No exception context in Python 2 + def _make_context_fixer(frame_exc): + return lambda new_exc, old_exc: None + + # Use 3 argument raise in Python 2, + # but use exec to avoid SyntaxError in Python 3 + def _reraise_with_existing_context(exc_details): + exc_type, exc_value, exc_tb = exc_details + exec("raise exc_type, exc_value, exc_tb") + +# Handle old-style classes if they exist +try: + from types import InstanceType +except ImportError: + # Python 3 doesn't have old-style classes + _get_type = type +else: + # Need to handle old-style context managers on Python 2 + def _get_type(obj): + obj_type = type(obj) + if obj_type is InstanceType: + return obj.__class__ # Old-style class + return obj_type # New-style class + + +# Inspired by discussions on http://bugs.python.org/issue13585 +class ExitStack(object): + """Context manager for dynamic management of a stack of exit callbacks + + For example: + + with ExitStack() as stack: + files = [stack.enter_context(open(fname)) for fname in filenames] + # All opened files will automatically be closed at the end of + # the with statement, even if attempts to open files later + # in the list raise an exception + + """ + def __init__(self): + self._exit_callbacks = deque() + + def pop_all(self): + """Preserve the context stack by transferring it to a new instance""" + new_stack = type(self)() + new_stack._exit_callbacks = self._exit_callbacks + self._exit_callbacks = deque() + return new_stack + + def _push_cm_exit(self, cm, cm_exit): + """Helper to correctly register callbacks to __exit__ methods""" + def _exit_wrapper(*exc_details): + return cm_exit(cm, *exc_details) + _exit_wrapper.__self__ = cm + self.push(_exit_wrapper) + + def push(self, exit): + """Registers a callback with the standard __exit__ method signature + + Can suppress exceptions the same way __exit__ methods can. + + Also accepts any object with an __exit__ method (registering a call + to the method instead of the object itself) + """ + # We use an unbound method rather than a bound method to follow + # the standard lookup behaviour for special methods + _cb_type = _get_type(exit) + try: + exit_method = _cb_type.__exit__ + except AttributeError: + # Not a context manager, so assume its a callable + self._exit_callbacks.append(exit) + else: + self._push_cm_exit(exit, exit_method) + return exit # Allow use as a decorator + + def callback(self, callback, *args, **kwds): + """Registers an arbitrary callback and arguments. + + Cannot suppress exceptions. + """ + def _exit_wrapper(exc_type, exc, tb): + callback(*args, **kwds) + # We changed the signature, so using @wraps is not appropriate, but + # setting __wrapped__ may still help with introspection + _exit_wrapper.__wrapped__ = callback + self.push(_exit_wrapper) + return callback # Allow use as a decorator + + def enter_context(self, cm): + """Enters the supplied context manager + + If successful, also pushes its __exit__ method as a callback and + returns the result of the __enter__ method. + """ + # We look up the special methods on the type to match the with statement + _cm_type = _get_type(cm) + _exit = _cm_type.__exit__ + result = _cm_type.__enter__(cm) + self._push_cm_exit(cm, _exit) + return result + + def close(self): + """Immediately unwind the context stack""" + self.__exit__(None, None, None) + + def __enter__(self): + return self + + def __exit__(self, *exc_details): + received_exc = exc_details[0] is not None + + # We manipulate the exception state so it behaves as though + # we were actually nesting multiple with statements + frame_exc = sys.exc_info()[1] + _fix_exception_context = _make_context_fixer(frame_exc) + + # Callbacks are invoked in LIFO order to match the behaviour of + # nested context managers + suppressed_exc = False + pending_raise = False + while self._exit_callbacks: + cb = self._exit_callbacks.pop() + try: + if cb(*exc_details): + suppressed_exc = True + pending_raise = False + exc_details = (None, None, None) + except: + new_exc_details = sys.exc_info() + # simulate the stack of exceptions by setting the context + _fix_exception_context(new_exc_details[1], exc_details[1]) + pending_raise = True + exc_details = new_exc_details + if pending_raise: + _reraise_with_existing_context(exc_details) + return received_exc and suppressed_exc + + +# Preserve backwards compatibility +class ContextStack(ExitStack): + """Backwards compatibility alias for ExitStack""" + + def __init__(self): + warnings.warn("ContextStack has been renamed to ExitStack", + DeprecationWarning) + super(ContextStack, self).__init__() + + def register_exit(self, callback): + return self.push(callback) + + def register(self, callback, *args, **kwds): + return self.callback(callback, *args, **kwds) + + def preserve(self): + return self.pop_all() + + +class nullcontext(AbstractContextManager): + """Context manager that does no additional processing. + Used as a stand-in for a normal context manager, when a particular + block of code is only sometimes used with a normal context manager: + cm = optional_cm if condition else nullcontext() + with cm: + # Perform operation, using optional_cm if condition is True + """ + + def __init__(self, enter_result=None): + self.enter_result = enter_result + + def __enter__(self): + return self.enter_result + + def __exit__(self, *excinfo): + pass diff --git a/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..f433b1a53f5b830a205fd2df78e2b34974656c7b --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/LICENSE.txt @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/METADATA b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..d914839b4b30b654d7b3d27e26ab3c7748ab0479 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/METADATA @@ -0,0 +1,187 @@ +Metadata-Version: 2.1 +Name: coverage +Version: 5.3.1 +Summary: Code coverage measurement for Python +Home-page: https://github.com/nedbat/coveragepy +Author: Ned Batchelder and 138 others +Author-email: ned@nedbatchelder.com +License: Apache 2.0 +Project-URL: Documentation, https://coverage.readthedocs.io +Project-URL: Funding, https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=pypi +Project-URL: Issues, https://github.com/nedbat/coveragepy/issues +Keywords: code coverage testing +Platform: UNKNOWN +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Quality Assurance +Classifier: Topic :: Software Development :: Testing +Classifier: Development Status :: 5 - Production/Stable +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4 +Description-Content-Type: text/x-rst +Provides-Extra: toml +Requires-Dist: toml ; extra == 'toml' + +.. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +.. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +=========== +Coverage.py +=========== + +Code coverage testing for Python. + +| |license| |versions| |status| +| |ci-status| |docs| |codecov| +| |kit| |format| |repos| |downloads| +| |stars| |forks| |contributors| +| |tidelift| |twitter-coveragepy| |twitter-nedbat| + +Coverage.py measures code coverage, typically during test execution. It uses +the code analysis tools and tracing hooks provided in the Python standard +library to determine which lines are executable, and which have been executed. + +Coverage.py runs on many versions of Python: + +* CPython 2.7. +* CPython 3.5 through 3.10 alpha. +* PyPy2 7.3.1 and PyPy3 7.3.1. + +Documentation is on `Read the Docs`_. Code repository and issue tracker are on +`GitHub`_. + +.. _Read the Docs: https://coverage.readthedocs.io/ +.. _GitHub: https://github.com/nedbat/coveragepy + + +**New in 5.x:** SQLite data storage, JSON report, contexts, relative filenames, +dropped support for Python 2.6, 3.3 and 3.4. + + +For Enterprise +-------------- + +.. |tideliftlogo| image:: https://nedbatchelder.com/pix/Tidelift_Logo_small.png + :alt: Tidelift + :target: https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme + +.. list-table:: + :widths: 10 100 + + * - |tideliftlogo| + - `Available as part of the Tidelift Subscription. `_ + Coverage and thousands of other packages are working with + Tidelift to deliver one enterprise subscription that covers all of the open + source you use. If you want the flexibility of open source and the confidence + of commercial-grade software, this is for you. + `Learn more. `_ + + +Getting Started +--------------- + +See the `Quick Start section`_ of the docs. + +.. _Quick Start section: https://coverage.readthedocs.io/#quick-start + + +Change history +-------------- + +The complete history of changes is on the `change history page`_. + +.. _change history page: https://coverage.readthedocs.io/en/latest/changes.html + + +Contributing +------------ + +See the `Contributing section`_ of the docs. + +.. _Contributing section: https://coverage.readthedocs.io/en/latest/contributing.html + + +Security +-------- + +To report a security vulnerability, please use the `Tidelift security +contact`_. Tidelift will coordinate the fix and disclosure. + +.. _Tidelift security contact: https://tidelift.com/security + + +License +------- + +Licensed under the `Apache 2.0 License`_. For details, see `NOTICE.txt`_. + +.. _Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0 +.. _NOTICE.txt: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + + +.. |ci-status| image:: https://github.com/nedbat/coveragepy/workflows/Test%20Suite/badge.svg + :target: https://github.com/nedbat/coveragepy/actions?query=workflow%3A%22Test+Suite%22 + :alt: Build status +.. |docs| image:: https://readthedocs.org/projects/coverage/badge/?version=latest&style=flat + :target: https://coverage.readthedocs.io/ + :alt: Documentation +.. |reqs| image:: https://requires.io/github/nedbat/coveragepy/requirements.svg?branch=master + :target: https://requires.io/github/nedbat/coveragepy/requirements/?branch=master + :alt: Requirements status +.. |kit| image:: https://badge.fury.io/py/coverage.svg + :target: https://pypi.org/project/coverage/ + :alt: PyPI status +.. |format| image:: https://img.shields.io/pypi/format/coverage.svg + :target: https://pypi.org/project/coverage/ + :alt: Kit format +.. |downloads| image:: https://img.shields.io/pypi/dw/coverage.svg + :target: https://pypi.org/project/coverage/ + :alt: Weekly PyPI downloads +.. |versions| image:: https://img.shields.io/pypi/pyversions/coverage.svg?logo=python&logoColor=FBE072 + :target: https://pypi.org/project/coverage/ + :alt: Python versions supported +.. |status| image:: https://img.shields.io/pypi/status/coverage.svg + :target: https://pypi.org/project/coverage/ + :alt: Package stability +.. |license| image:: https://img.shields.io/pypi/l/coverage.svg + :target: https://pypi.org/project/coverage/ + :alt: License +.. |codecov| image:: https://codecov.io/github/nedbat/coveragepy/coverage.svg?branch=master&precision=2 + :target: https://codecov.io/github/nedbat/coveragepy?branch=master + :alt: Coverage! +.. |repos| image:: https://repology.org/badge/tiny-repos/python:coverage.svg + :target: https://repology.org/metapackage/python:coverage/versions + :alt: Packaging status +.. |tidelift| image:: https://tidelift.com/badges/package/pypi/coverage + :target: https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme + :alt: Tidelift +.. |stars| image:: https://img.shields.io/github/stars/nedbat/coveragepy.svg?logo=github + :target: https://github.com/nedbat/coveragepy/stargazers + :alt: Github stars +.. |forks| image:: https://img.shields.io/github/forks/nedbat/coveragepy.svg?logo=github + :target: https://github.com/nedbat/coveragepy/network/members + :alt: Github forks +.. |contributors| image:: https://img.shields.io/github/contributors/nedbat/coveragepy.svg?logo=github + :target: https://github.com/nedbat/coveragepy/graphs/contributors + :alt: Contributors +.. |twitter-coveragepy| image:: https://img.shields.io/twitter/follow/coveragepy.svg?label=coveragepy&style=flat&logo=twitter&logoColor=4FADFF + :target: https://twitter.com/coveragepy + :alt: coverage.py on Twitter +.. |twitter-nedbat| image:: https://img.shields.io/twitter/follow/nedbat.svg?label=nedbat&style=flat&logo=twitter&logoColor=4FADFF + :target: https://twitter.com/nedbat + :alt: nedbat on Twitter + + diff --git a/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/RECORD b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..409dd80c4c5ac9083274790eabbdf0e9303bb0d4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/RECORD @@ -0,0 +1,102 @@ +../../../bin/coverage,sha256=3RapJ1VoDbUUGIKUWlHP78ePV0qP7nIHP6BI3wOF_58,259 +../../../bin/coverage-3.8,sha256=3RapJ1VoDbUUGIKUWlHP78ePV0qP7nIHP6BI3wOF_58,259 +../../../bin/coverage3,sha256=3RapJ1VoDbUUGIKUWlHP78ePV0qP7nIHP6BI3wOF_58,259 +coverage-5.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +coverage-5.3.1.dist-info/LICENSE.txt,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174 +coverage-5.3.1.dist-info/METADATA,sha256=9ZgGnS9QeRJKNJ9AnZYBsJhaCJCJhXzGiQpvIFTxw88,7691 +coverage-5.3.1.dist-info/RECORD,, +coverage-5.3.1.dist-info/WHEEL,sha256=3Rn77v1cOIQWUJHbJxwJJ4HycjuBQ6DIO-hbFDX-kLU,111 +coverage-5.3.1.dist-info/entry_points.txt,sha256=ldb8Seg6KXEyNviat4q5qt4neooZgn7PybptoyVb6YA,123 +coverage-5.3.1.dist-info/top_level.txt,sha256=BjhyiIvusb5OJkqCXjRncTF3soKF-mDOby-hxkWwwv0,9 +coverage/__init__.py,sha256=8KDKMbvJG6vWupvHNzzHUIV906S6IPu6-vRdWnZg3fI,1283 +coverage/__main__.py,sha256=IOd5fAsdpJd1t8ZyrkGcFk-eqMd3Sdc2qbhNb8YQBW0,257 +coverage/__pycache__/__init__.cpython-38.pyc,, +coverage/__pycache__/__main__.cpython-38.pyc,, +coverage/__pycache__/annotate.cpython-38.pyc,, +coverage/__pycache__/backunittest.cpython-38.pyc,, +coverage/__pycache__/backward.cpython-38.pyc,, +coverage/__pycache__/bytecode.cpython-38.pyc,, +coverage/__pycache__/cmdline.cpython-38.pyc,, +coverage/__pycache__/collector.cpython-38.pyc,, +coverage/__pycache__/config.cpython-38.pyc,, +coverage/__pycache__/context.cpython-38.pyc,, +coverage/__pycache__/control.cpython-38.pyc,, +coverage/__pycache__/data.cpython-38.pyc,, +coverage/__pycache__/debug.cpython-38.pyc,, +coverage/__pycache__/disposition.cpython-38.pyc,, +coverage/__pycache__/env.cpython-38.pyc,, +coverage/__pycache__/execfile.cpython-38.pyc,, +coverage/__pycache__/files.cpython-38.pyc,, +coverage/__pycache__/html.cpython-38.pyc,, +coverage/__pycache__/inorout.cpython-38.pyc,, +coverage/__pycache__/jsonreport.cpython-38.pyc,, +coverage/__pycache__/misc.cpython-38.pyc,, +coverage/__pycache__/multiproc.cpython-38.pyc,, +coverage/__pycache__/numbits.cpython-38.pyc,, +coverage/__pycache__/optional.cpython-38.pyc,, +coverage/__pycache__/parser.cpython-38.pyc,, +coverage/__pycache__/phystokens.cpython-38.pyc,, +coverage/__pycache__/plugin.cpython-38.pyc,, +coverage/__pycache__/plugin_support.cpython-38.pyc,, +coverage/__pycache__/python.cpython-38.pyc,, +coverage/__pycache__/pytracer.cpython-38.pyc,, +coverage/__pycache__/report.cpython-38.pyc,, +coverage/__pycache__/results.cpython-38.pyc,, +coverage/__pycache__/sqldata.cpython-38.pyc,, +coverage/__pycache__/summary.cpython-38.pyc,, +coverage/__pycache__/templite.cpython-38.pyc,, +coverage/__pycache__/tomlconfig.cpython-38.pyc,, +coverage/__pycache__/version.cpython-38.pyc,, +coverage/__pycache__/xmlreport.cpython-38.pyc,, +coverage/annotate.py,sha256=FCS90-FKxNZ1wUBNmGZvFp0f0fsxL3r9jjgA5CDG8VY,3557 +coverage/backunittest.py,sha256=j-RxGNTt7lXgLVZD0_Y7hmHSMeXHb6KNRbf09-soioc,1118 +coverage/backward.py,sha256=sZsyWckKHL8YJhnNk0Ag33hof2_NVtnocOgYcEJQ7Ik,7425 +coverage/bytecode.py,sha256=RgL-pG1AicUD8ksZ6P_swwfzlTJldCrBvr4oZ-G2Qyc,609 +coverage/cmdline.py,sha256=WaSXjsQgN1WZt-YLNYMZq8oIvRxIb7PQ1soURnmNDfQ,30534 +coverage/collector.py,sha256=wAmpepWPFmRTa6TBWZCAWedpQaCxtKEfByyTS3zn3rQ,17857 +coverage/config.py,sha256=gkoJivwvg3eOuwm1925LEoj3RFH7cMTraJSyim6Dw7E,19240 +coverage/context.py,sha256=aG4mZVyQROCGqlLhCeKYtNErn6Ai2mG3_byN6ZjjBTo,3091 +coverage/control.py,sha256=k3XzIc9FpqV_qQ-zLEodaC1OE1Xgxx_qOP-34X1WqtE,42135 +coverage/data.py,sha256=9DIKvHFfsfYjiaM_DGhSbi-6dS4Hz4wBN9es0jlV6iI,4479 +coverage/debug.py,sha256=5J6qm0OA2RYCofBk-V-88X1RaPr3gniiFdwgew1Vu28,13872 +coverage/disposition.py,sha256=HF2eJULSz9NEMOgN22daJJvgBzJtvEKtsj2gw8ZK26E,1255 +coverage/env.py,sha256=cKBmamRoCKdgeswsMGKhNJwQla-HAeMZUYU5IuSagWM,3581 +coverage/execfile.py,sha256=sewNsJ4z9GNJVxscKCTz0e_BQsh6npoj1sHNZWlIf04,13444 +coverage/files.py,sha256=JAieZ9n2cXLT86-o5UlsNH-IS_-Z6XdVecP2DJwIPPI,14249 +coverage/fullcoverage/__pycache__/encodings.cpython-38.pyc,, +coverage/fullcoverage/encodings.py,sha256=DW3X3yoqrI-Waq3l3lgGdIxLFia4gsrjLR03cr_EWX8,2548 +coverage/html.py,sha256=2tzrtCk6b9oZVRCBYaz_Q7UHRC5K0zLcXJMkhGvqqus,17595 +coverage/htmlfiles/coverage_html.js,sha256=s2flitWxsFyvZrtii2YU-6aGfYuOxzNaFzznQJ7YT98,18626 +coverage/htmlfiles/favicon_32.png,sha256=vIEA-odDwRvSQ-syWfSwEnWGUWEv2b-Tv4tzTRfwJWE,1732 +coverage/htmlfiles/index.html,sha256=h-P_bm8PsAdVIbWd4JxZvapsVShntxV7b6VLwqrQfbM,4254 +coverage/htmlfiles/jquery.ba-throttle-debounce.min.js,sha256=wXepUsOX1VYr5AyWGbIoAqlrvjQz9unPbtEyM7wFBnw,731 +coverage/htmlfiles/jquery.hotkeys.js,sha256=VVqbRGJlCvARPGMJXcSiRRIJwkpged3wG5fQ47gi42U,3065 +coverage/htmlfiles/jquery.isonscreen.js,sha256=WEyXE6yTYNzAYfzViwksNu7AJAY5zZBI9q6-J9pF1Zw,1502 +coverage/htmlfiles/jquery.min.js,sha256=JCYrqv7xcJKSfD2v52SqpSoqNxuD7SJJzKfkFN-Z-sE,95785 +coverage/htmlfiles/jquery.tablesorter.min.js,sha256=t4ifnz2eByQEUafncoSdJUwD2jUt68VY8CzNjAywo08,12795 +coverage/htmlfiles/keybd_closed.png,sha256=FNDmw-Lx9aptnMr-XiZ4gh2EGPs17nft-QKmXgilIe0,112 +coverage/htmlfiles/keybd_open.png,sha256=FNDmw-Lx9aptnMr-XiZ4gh2EGPs17nft-QKmXgilIe0,112 +coverage/htmlfiles/pyfile.html,sha256=kEAzajng44FAbiltAh13XoKAihtJXvlpIBtn71btdQw,4621 +coverage/htmlfiles/style.css,sha256=9uZy22IerLz4-NhzResJnY1qJMf6ZqmPyvrxdD0zk20,11680 +coverage/htmlfiles/style.scss,sha256=W4111nKz-avcwaVcav-1fnfWZGHy-iogK2NR-I3KrvE,16263 +coverage/inorout.py,sha256=I2VG99xtlde8jVvVeerNiPIX-GCraUsJJ6cpWq_I36o,19752 +coverage/jsonreport.py,sha256=PZVkril1YUHeQSc7ASNjWxfEKdnMWZeo22Vy6TNrgw0,3686 +coverage/misc.py,sha256=RwVhhUPd2Usc16skWZQx8R1-LavP060D3hQbf6F0WI0,11014 +coverage/multiproc.py,sha256=F0sCnYpguFWqbqNB75gOWjN3_KapBVrkTR7-qUUeD6I,3886 +coverage/numbits.py,sha256=kPD3WHU9orL6cl5FB56eF5Dz5QQgYUR0tMadI4RDHd8,5529 +coverage/optional.py,sha256=AVIUvod7uTNlXHYrr40pHXDxLy2e9-wzOcqJWdzG-vI,1785 +coverage/parser.py,sha256=XPyxwHNH2C6KvPboDSrTN42H5Ihz0rG7lVrv4uAA4Tc,49029 +coverage/phystokens.py,sha256=e7tvO3Ro4Kdpd76X7OOonUjsCs3ZeFIHImGC_szL0kI,10050 +coverage/plugin.py,sha256=-RMXO-rY06qwW9FWZBO92q1b1ABGNmyNqeKbIALvnOE,18549 +coverage/plugin_support.py,sha256=-MQ_4TSZug4u6ReNqNdb1CnAlBHkJH5h5HO03o4HgdI,9101 +coverage/python.py,sha256=LsqDDXMc1qeVxeez058NGTnlSG3xQiSoSa6De04OgrM,7633 +coverage/pytracer.py,sha256=jd2pUeLkBFClj1XbrGkMf1BuJWdLr1PGsGQeUuI2yLE,10341 +coverage/report.py,sha256=AvFOS1l_kr0qMBpaQTOzV-tgkrt4N2CvMJAtBc0ZpSQ,3222 +coverage/results.py,sha256=xiBWzHHgxdJPeOIZFTZKtkWYWZ6jLbRVj18WMJaY_0U,12227 +coverage/sqldata.py,sha256=gDVt3DoBwnW9Dyz-U7IxfCT1clqHcOsHSAnutyY4KaU,43841 +coverage/summary.py,sha256=e_Im9ZvJR4clMFz2RyyFKAiBz1MhXKrZvGjB11n0BFA,6492 +coverage/templite.py,sha256=ZrsnMuc0rlMVYb54KGA8Whp1CMh5XsGeu3_7aW-OkBE,10504 +coverage/tomlconfig.py,sha256=xY7G5Q3r6IJPs8e_5taPAWlksgk7jF6cRMQknIp8axM,5468 +coverage/tracer.cpython-38-x86_64-linux-gnu.so,sha256=pzxG37qupju2wrbCmpbzQayuB_KVDqx4Ij8YlowPgDU,147224 +coverage/version.py,sha256=M-ueIYEVham7ZeiHQPZQHBc69PC-NnGo5IR5qOmChoE,1260 +coverage/xmlreport.py,sha256=ogkVOk8rLmFmPh5EaKWxSgQQabxjvUhcTLmTjrV-y1A,8700 diff --git a/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/WHEEL b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..3f8f7734c20561d26e697b5fd9781be3479df20f --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.35.1) +Root-Is-Purelib: false +Tag: cp38-cp38-manylinux2010_x86_64 + diff --git a/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/entry_points.txt b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..58d31949dedde135ff3bcd216d67b2c10f3c9ca3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/entry_points.txt @@ -0,0 +1,5 @@ +[console_scripts] +coverage = coverage.cmdline:main +coverage-3.8 = coverage.cmdline:main +coverage3 = coverage.cmdline:main + diff --git a/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..4ebc8aea50e0a67e000ba29a30809d0a7b9b2666 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage-5.3.1.dist-info/top_level.txt @@ -0,0 +1 @@ +coverage diff --git a/robot/lib/python3.8/site-packages/coverage/__init__.py b/robot/lib/python3.8/site-packages/coverage/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..331b304b68325ac6c6d53f7f2248c25d070f5156 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/__init__.py @@ -0,0 +1,36 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Code coverage measurement for Python. + +Ned Batchelder +https://nedbatchelder.com/code/coverage + +""" + +import sys + +from coverage.version import __version__, __url__, version_info + +from coverage.control import Coverage, process_startup +from coverage.data import CoverageData +from coverage.misc import CoverageException +from coverage.plugin import CoveragePlugin, FileTracer, FileReporter +from coverage.pytracer import PyTracer + +# Backward compatibility. +coverage = Coverage + +# On Windows, we encode and decode deep enough that something goes wrong and +# the encodings.utf_8 module is loaded and then unloaded, I don't know why. +# Adding a reference here prevents it from being unloaded. Yuk. +import encodings.utf_8 # pylint: disable=wrong-import-position, wrong-import-order + +# Because of the "from coverage.control import fooey" lines at the top of the +# file, there's an entry for coverage.coverage in sys.modules, mapped to None. +# This makes some inspection tools (like pydoc) unable to find the class +# coverage.coverage. So remove that entry. +try: + del sys.modules['coverage.coverage'] +except KeyError: + pass diff --git a/robot/lib/python3.8/site-packages/coverage/__main__.py b/robot/lib/python3.8/site-packages/coverage/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..79aa4e2b35d798e911fd677c78bb59b0ed556cf5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/__main__.py @@ -0,0 +1,8 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Coverage.py's main entry point.""" + +import sys +from coverage.cmdline import main +sys.exit(main()) diff --git a/robot/lib/python3.8/site-packages/coverage/annotate.py b/robot/lib/python3.8/site-packages/coverage/annotate.py new file mode 100644 index 0000000000000000000000000000000000000000..999ab6e557d68c1f98b1115f4aaf3ca3a9881d36 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/annotate.py @@ -0,0 +1,108 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Source file annotation for coverage.py.""" + +import io +import os +import re + +from coverage.files import flat_rootname +from coverage.misc import ensure_dir, isolate_module +from coverage.report import get_analysis_to_report + +os = isolate_module(os) + + +class AnnotateReporter(object): + """Generate annotated source files showing line coverage. + + This reporter creates annotated copies of the measured source files. Each + .py file is copied as a .py,cover file, with a left-hand margin annotating + each line:: + + > def h(x): + - if 0: #pragma: no cover + - pass + > if x == 1: + ! a = 1 + > else: + > a = 2 + + > h(2) + + Executed lines use '>', lines not executed use '!', lines excluded from + consideration use '-'. + + """ + + def __init__(self, coverage): + self.coverage = coverage + self.config = self.coverage.config + self.directory = None + + blank_re = re.compile(r"\s*(#|$)") + else_re = re.compile(r"\s*else\s*:\s*(#|$)") + + def report(self, morfs, directory=None): + """Run the report. + + See `coverage.report()` for arguments. + + """ + self.directory = directory + self.coverage.get_data() + for fr, analysis in get_analysis_to_report(self.coverage, morfs): + self.annotate_file(fr, analysis) + + def annotate_file(self, fr, analysis): + """Annotate a single file. + + `fr` is the FileReporter for the file to annotate. + + """ + statements = sorted(analysis.statements) + missing = sorted(analysis.missing) + excluded = sorted(analysis.excluded) + + if self.directory: + ensure_dir(self.directory) + dest_file = os.path.join(self.directory, flat_rootname(fr.relative_filename())) + if dest_file.endswith("_py"): + dest_file = dest_file[:-3] + ".py" + dest_file += ",cover" + else: + dest_file = fr.filename + ",cover" + + with io.open(dest_file, 'w', encoding='utf8') as dest: + i = 0 + j = 0 + covered = True + source = fr.source() + for lineno, line in enumerate(source.splitlines(True), start=1): + while i < len(statements) and statements[i] < lineno: + i += 1 + while j < len(missing) and missing[j] < lineno: + j += 1 + if i < len(statements) and statements[i] == lineno: + covered = j >= len(missing) or missing[j] > lineno + if self.blank_re.match(line): + dest.write(u' ') + elif self.else_re.match(line): + # Special logic for lines containing only 'else:'. + if i >= len(statements) and j >= len(missing): + dest.write(u'! ') + elif i >= len(statements) or j >= len(missing): + dest.write(u'> ') + elif statements[i] == missing[j]: + dest.write(u'! ') + else: + dest.write(u'> ') + elif lineno in excluded: + dest.write(u'- ') + elif covered: + dest.write(u'> ') + else: + dest.write(u'! ') + + dest.write(line) diff --git a/robot/lib/python3.8/site-packages/coverage/backunittest.py b/robot/lib/python3.8/site-packages/coverage/backunittest.py new file mode 100644 index 0000000000000000000000000000000000000000..123bb2a1370814ff9ce94c43f76a9cdbf150962c --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/backunittest.py @@ -0,0 +1,33 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Implementations of unittest features from the future.""" + +import unittest + + +def unittest_has(method): + """Does `unittest.TestCase` have `method` defined?""" + return hasattr(unittest.TestCase, method) + + +class TestCase(unittest.TestCase): + """Just like unittest.TestCase, but with assert methods added. + + Designed to be compatible with 3.1 unittest. Methods are only defined if + `unittest` doesn't have them. + + """ + # pylint: disable=signature-differs, deprecated-method + + if not unittest_has('assertCountEqual'): + def assertCountEqual(self, *args, **kwargs): + return self.assertItemsEqual(*args, **kwargs) + + if not unittest_has('assertRaisesRegex'): + def assertRaisesRegex(self, *args, **kwargs): + return self.assertRaisesRegexp(*args, **kwargs) + + if not unittest_has('assertRegex'): + def assertRegex(self, *args, **kwargs): + return self.assertRegexpMatches(*args, **kwargs) diff --git a/robot/lib/python3.8/site-packages/coverage/backward.py b/robot/lib/python3.8/site-packages/coverage/backward.py new file mode 100644 index 0000000000000000000000000000000000000000..9d1d78e5b366759889359daf5144e5fac6e51557 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/backward.py @@ -0,0 +1,266 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Add things to old Pythons so I can pretend they are newer.""" + +# This file's purpose is to provide modules to be imported from here. +# pylint: disable=unused-import + +import os +import sys + +from datetime import datetime + +from coverage import env + + +# Pythons 2 and 3 differ on where to get StringIO. +try: + from cStringIO import StringIO +except ImportError: + from io import StringIO + +# In py3, ConfigParser was renamed to the more-standard configparser. +# But there's a py3 backport that installs "configparser" in py2, and I don't +# want it because it has annoying deprecation warnings. So try the real py2 +# import first. +try: + import ConfigParser as configparser +except ImportError: + import configparser + +# What's a string called? +try: + string_class = basestring +except NameError: + string_class = str + +# What's a Unicode string called? +try: + unicode_class = unicode +except NameError: + unicode_class = str + +# range or xrange? +try: + range = xrange # pylint: disable=redefined-builtin +except NameError: + range = range + +try: + from itertools import zip_longest +except ImportError: + from itertools import izip_longest as zip_longest + +# Where do we get the thread id from? +try: + from thread import get_ident as get_thread_id +except ImportError: + from threading import get_ident as get_thread_id + +try: + os.PathLike +except AttributeError: + # This is Python 2 and 3 + path_types = (bytes, string_class, unicode_class) +else: + # 3.6+ + path_types = (bytes, str, os.PathLike) + +# shlex.quote is new, but there's an undocumented implementation in "pipes", +# who knew!? +try: + from shlex import quote as shlex_quote +except ImportError: + # Useful function, available under a different (undocumented) name + # in Python versions earlier than 3.3. + from pipes import quote as shlex_quote + +try: + import reprlib +except ImportError: + import repr as reprlib + +# A function to iterate listlessly over a dict's items, and one to get the +# items as a list. +try: + {}.iteritems +except AttributeError: + # Python 3 + def iitems(d): + """Produce the items from dict `d`.""" + return d.items() + + def litems(d): + """Return a list of items from dict `d`.""" + return list(d.items()) +else: + # Python 2 + def iitems(d): + """Produce the items from dict `d`.""" + return d.iteritems() + + def litems(d): + """Return a list of items from dict `d`.""" + return d.items() + +# Getting the `next` function from an iterator is different in 2 and 3. +try: + iter([]).next +except AttributeError: + def iternext(seq): + """Get the `next` function for iterating over `seq`.""" + return iter(seq).__next__ +else: + def iternext(seq): + """Get the `next` function for iterating over `seq`.""" + return iter(seq).next + +# Python 3.x is picky about bytes and strings, so provide methods to +# get them right, and make them no-ops in 2.x +if env.PY3: + def to_bytes(s): + """Convert string `s` to bytes.""" + return s.encode('utf8') + + def to_string(b): + """Convert bytes `b` to string.""" + return b.decode('utf8') + + def binary_bytes(byte_values): + """Produce a byte string with the ints from `byte_values`.""" + return bytes(byte_values) + + def byte_to_int(byte): + """Turn a byte indexed from a bytes object into an int.""" + return byte + + def bytes_to_ints(bytes_value): + """Turn a bytes object into a sequence of ints.""" + # In Python 3, iterating bytes gives ints. + return bytes_value + +else: + def to_bytes(s): + """Convert string `s` to bytes (no-op in 2.x).""" + return s + + def to_string(b): + """Convert bytes `b` to string.""" + return b + + def binary_bytes(byte_values): + """Produce a byte string with the ints from `byte_values`.""" + return "".join(chr(b) for b in byte_values) + + def byte_to_int(byte): + """Turn a byte indexed from a bytes object into an int.""" + return ord(byte) + + def bytes_to_ints(bytes_value): + """Turn a bytes object into a sequence of ints.""" + for byte in bytes_value: + yield ord(byte) + + +try: + # In Python 2.x, the builtins were in __builtin__ + BUILTINS = sys.modules['__builtin__'] +except KeyError: + # In Python 3.x, they're in builtins + BUILTINS = sys.modules['builtins'] + + +# imp was deprecated in Python 3.3 +try: + import importlib + import importlib.util + imp = None +except ImportError: + importlib = None + +# We only want to use importlib if it has everything we need. +try: + importlib_util_find_spec = importlib.util.find_spec +except Exception: + import imp + importlib_util_find_spec = None + +# What is the .pyc magic number for this version of Python? +try: + PYC_MAGIC_NUMBER = importlib.util.MAGIC_NUMBER +except AttributeError: + PYC_MAGIC_NUMBER = imp.get_magic() + + +def code_object(fn): + """Get the code object from a function.""" + try: + return fn.func_code + except AttributeError: + return fn.__code__ + + +try: + from types import SimpleNamespace +except ImportError: + # The code from https://docs.python.org/3/library/types.html#types.SimpleNamespace + class SimpleNamespace: + """Python implementation of SimpleNamespace, for Python 2.""" + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def __repr__(self): + keys = sorted(self.__dict__) + items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys) + return "{}({})".format(type(self).__name__, ", ".join(items)) + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + + +def format_local_datetime(dt): + """Return a string with local timezone representing the date. + If python version is lower than 3.6, the time zone is not included. + """ + try: + return dt.astimezone().strftime('%Y-%m-%d %H:%M %z') + except (TypeError, ValueError): + # Datetime.astimezone in Python 3.5 can not handle naive datetime + return dt.strftime('%Y-%m-%d %H:%M') + + +def invalidate_import_caches(): + """Invalidate any import caches that may or may not exist.""" + if importlib and hasattr(importlib, "invalidate_caches"): + importlib.invalidate_caches() + + +def import_local_file(modname, modfile=None): + """Import a local file as a module. + + Opens a file in the current directory named `modname`.py, imports it + as `modname`, and returns the module object. `modfile` is the file to + import if it isn't in the current directory. + + """ + try: + from importlib.machinery import SourceFileLoader + except ImportError: + SourceFileLoader = None + + if modfile is None: + modfile = modname + '.py' + if SourceFileLoader: + # pylint: disable=no-value-for-parameter, deprecated-method + mod = SourceFileLoader(modname, modfile).load_module() + else: + for suff in imp.get_suffixes(): # pragma: part covered + if suff[0] == '.py': + break + + with open(modfile, 'r') as f: + # pylint: disable=undefined-loop-variable + mod = imp.load_module(modname, f, modfile, suff) + + return mod diff --git a/robot/lib/python3.8/site-packages/coverage/bytecode.py b/robot/lib/python3.8/site-packages/coverage/bytecode.py new file mode 100644 index 0000000000000000000000000000000000000000..ceb18cf3740d895e04731f069ab9b49337fb1c21 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/bytecode.py @@ -0,0 +1,19 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Bytecode manipulation for coverage.py""" + +import types + + +def code_objects(code): + """Iterate over all the code objects in `code`.""" + stack = [code] + while stack: + # We're going to return the code object on the stack, but first + # push its children for later returning. + code = stack.pop() + for c in code.co_consts: + if isinstance(c, types.CodeType): + stack.append(c) + yield code diff --git a/robot/lib/python3.8/site-packages/coverage/cmdline.py b/robot/lib/python3.8/site-packages/coverage/cmdline.py new file mode 100644 index 0000000000000000000000000000000000000000..9c9ae868aaa91466b113174d2243a2383f7db92c --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/cmdline.py @@ -0,0 +1,904 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Command-line support for coverage.py.""" + +from __future__ import print_function + +import glob +import optparse +import os.path +import shlex +import sys +import textwrap +import traceback + +import coverage +from coverage import Coverage +from coverage import env +from coverage.collector import CTracer +from coverage.data import line_counts +from coverage.debug import info_formatter, info_header, short_stack +from coverage.execfile import PyRunner +from coverage.misc import BaseCoverageException, ExceptionDuringRun, NoSource, output_encoding +from coverage.results import should_fail_under + + +class Opts(object): + """A namespace class for individual options we'll build parsers from.""" + + append = optparse.make_option( + '-a', '--append', action='store_true', + help="Append coverage data to .coverage, otherwise it starts clean each time.", + ) + branch = optparse.make_option( + '', '--branch', action='store_true', + help="Measure branch coverage in addition to statement coverage.", + ) + CONCURRENCY_CHOICES = [ + "thread", "gevent", "greenlet", "eventlet", "multiprocessing", + ] + concurrency = optparse.make_option( + '', '--concurrency', action='store', metavar="LIB", + choices=CONCURRENCY_CHOICES, + help=( + "Properly measure code using a concurrency library. " + "Valid values are: %s." + ) % ", ".join(CONCURRENCY_CHOICES), + ) + context = optparse.make_option( + '', '--context', action='store', metavar="LABEL", + help="The context label to record for this coverage run.", + ) + debug = optparse.make_option( + '', '--debug', action='store', metavar="OPTS", + help="Debug options, separated by commas. [env: COVERAGE_DEBUG]", + ) + directory = optparse.make_option( + '-d', '--directory', action='store', metavar="DIR", + help="Write the output files to DIR.", + ) + fail_under = optparse.make_option( + '', '--fail-under', action='store', metavar="MIN", type="float", + help="Exit with a status of 2 if the total coverage is less than MIN.", + ) + help = optparse.make_option( + '-h', '--help', action='store_true', + help="Get help on this command.", + ) + ignore_errors = optparse.make_option( + '-i', '--ignore-errors', action='store_true', + help="Ignore errors while reading source files.", + ) + include = optparse.make_option( + '', '--include', action='store', + metavar="PAT1,PAT2,...", + help=( + "Include only files whose paths match one of these patterns. " + "Accepts shell-style wildcards, which must be quoted." + ), + ) + pylib = optparse.make_option( + '-L', '--pylib', action='store_true', + help=( + "Measure coverage even inside the Python installed library, " + "which isn't done by default." + ), + ) + sort = optparse.make_option( + '--sort', action='store', metavar='COLUMN', + help="Sort the report by the named column: name, stmts, miss, branch, brpart, or cover. " + "Default is name." + ) + show_missing = optparse.make_option( + '-m', '--show-missing', action='store_true', + help="Show line numbers of statements in each module that weren't executed.", + ) + skip_covered = optparse.make_option( + '--skip-covered', action='store_true', + help="Skip files with 100% coverage.", + ) + no_skip_covered = optparse.make_option( + '--no-skip-covered', action='store_false', dest='skip_covered', + help="Disable --skip-covered.", + ) + skip_empty = optparse.make_option( + '--skip-empty', action='store_true', + help="Skip files with no code.", + ) + show_contexts = optparse.make_option( + '--show-contexts', action='store_true', + help="Show contexts for covered lines.", + ) + omit = optparse.make_option( + '', '--omit', action='store', + metavar="PAT1,PAT2,...", + help=( + "Omit files whose paths match one of these patterns. " + "Accepts shell-style wildcards, which must be quoted." + ), + ) + contexts = optparse.make_option( + '', '--contexts', action='store', + metavar="REGEX1,REGEX2,...", + help=( + "Only display data from lines covered in the given contexts. " + "Accepts Python regexes, which must be quoted." + ), + ) + output_xml = optparse.make_option( + '-o', '', action='store', dest="outfile", + metavar="OUTFILE", + help="Write the XML report to this file. Defaults to 'coverage.xml'", + ) + output_json = optparse.make_option( + '-o', '', action='store', dest="outfile", + metavar="OUTFILE", + help="Write the JSON report to this file. Defaults to 'coverage.json'", + ) + json_pretty_print = optparse.make_option( + '', '--pretty-print', action='store_true', + help="Format the JSON for human readers.", + ) + parallel_mode = optparse.make_option( + '-p', '--parallel-mode', action='store_true', + help=( + "Append the machine name, process id and random number to the " + ".coverage data file name to simplify collecting data from " + "many processes." + ), + ) + module = optparse.make_option( + '-m', '--module', action='store_true', + help=( + " is an importable Python module, not a script path, " + "to be run as 'python -m' would run it." + ), + ) + precision = optparse.make_option( + '', '--precision', action='store', metavar='N', type=int, + help=( + "Number of digits after the decimal point to display for " + "reported coverage percentages." + ), + ) + rcfile = optparse.make_option( + '', '--rcfile', action='store', + help=( + "Specify configuration file. " + "By default '.coveragerc', 'setup.cfg', 'tox.ini', and " + "'pyproject.toml' are tried. [env: COVERAGE_RCFILE]" + ), + ) + source = optparse.make_option( + '', '--source', action='store', metavar="SRC1,SRC2,...", + help="A list of packages or directories of code to be measured.", + ) + timid = optparse.make_option( + '', '--timid', action='store_true', + help=( + "Use a simpler but slower trace method. Try this if you get " + "seemingly impossible results!" + ), + ) + title = optparse.make_option( + '', '--title', action='store', metavar="TITLE", + help="A text string to use as the title on the HTML.", + ) + version = optparse.make_option( + '', '--version', action='store_true', + help="Display version information and exit.", + ) + + +class CoverageOptionParser(optparse.OptionParser, object): + """Base OptionParser for coverage.py. + + Problems don't exit the program. + Defaults are initialized for all options. + + """ + + def __init__(self, *args, **kwargs): + super(CoverageOptionParser, self).__init__( + add_help_option=False, *args, **kwargs + ) + self.set_defaults( + action=None, + append=None, + branch=None, + concurrency=None, + context=None, + debug=None, + directory=None, + fail_under=None, + help=None, + ignore_errors=None, + include=None, + module=None, + omit=None, + contexts=None, + parallel_mode=None, + precision=None, + pylib=None, + rcfile=True, + show_missing=None, + skip_covered=None, + skip_empty=None, + show_contexts=None, + sort=None, + source=None, + timid=None, + title=None, + version=None, + ) + + self.disable_interspersed_args() + + class OptionParserError(Exception): + """Used to stop the optparse error handler ending the process.""" + pass + + def parse_args_ok(self, args=None, options=None): + """Call optparse.parse_args, but return a triple: + + (ok, options, args) + + """ + try: + options, args = super(CoverageOptionParser, self).parse_args(args, options) + except self.OptionParserError: + return False, None, None + return True, options, args + + def error(self, msg): + """Override optparse.error so sys.exit doesn't get called.""" + show_help(msg) + raise self.OptionParserError + + +class GlobalOptionParser(CoverageOptionParser): + """Command-line parser for coverage.py global option arguments.""" + + def __init__(self): + super(GlobalOptionParser, self).__init__() + + self.add_options([ + Opts.help, + Opts.version, + ]) + + +class CmdOptionParser(CoverageOptionParser): + """Parse one of the new-style commands for coverage.py.""" + + def __init__(self, action, options, defaults=None, usage=None, description=None): + """Create an OptionParser for a coverage.py command. + + `action` is the slug to put into `options.action`. + `options` is a list of Option's for the command. + `defaults` is a dict of default value for options. + `usage` is the usage string to display in help. + `description` is the description of the command, for the help text. + + """ + if usage: + usage = "%prog " + usage + super(CmdOptionParser, self).__init__( + usage=usage, + description=description, + ) + self.set_defaults(action=action, **(defaults or {})) + self.add_options(options) + self.cmd = action + + def __eq__(self, other): + # A convenience equality, so that I can put strings in unit test + # results, and they will compare equal to objects. + return (other == "" % self.cmd) + + __hash__ = None # This object doesn't need to be hashed. + + def get_prog_name(self): + """Override of an undocumented function in optparse.OptionParser.""" + program_name = super(CmdOptionParser, self).get_prog_name() + + # Include the sub-command for this parser as part of the command. + return "{command} {subcommand}".format(command=program_name, subcommand=self.cmd) + + +GLOBAL_ARGS = [ + Opts.debug, + Opts.help, + Opts.rcfile, + ] + +CMDS = { + 'annotate': CmdOptionParser( + "annotate", + [ + Opts.directory, + Opts.ignore_errors, + Opts.include, + Opts.omit, + ] + GLOBAL_ARGS, + usage="[options] [modules]", + description=( + "Make annotated copies of the given files, marking statements that are executed " + "with > and statements that are missed with !." + ), + ), + + 'combine': CmdOptionParser( + "combine", + [ + Opts.append, + ] + GLOBAL_ARGS, + usage="[options] ... ", + description=( + "Combine data from multiple coverage files collected " + "with 'run -p'. The combined results are written to a single " + "file representing the union of the data. The positional " + "arguments are data files or directories containing data files. " + "If no paths are provided, data files in the default data file's " + "directory are combined." + ), + ), + + 'debug': CmdOptionParser( + "debug", GLOBAL_ARGS, + usage="", + description=( + "Display information about the internals of coverage.py, " + "for diagnosing problems. " + "Topics are: " + "'data' to show a summary of the collected data; " + "'sys' to show installation information; " + "'config' to show the configuration; " + "'premain' to show what is calling coverage." + ), + ), + + 'erase': CmdOptionParser( + "erase", GLOBAL_ARGS, + description="Erase previously collected coverage data.", + ), + + 'help': CmdOptionParser( + "help", GLOBAL_ARGS, + usage="[command]", + description="Describe how to use coverage.py", + ), + + 'html': CmdOptionParser( + "html", + [ + Opts.contexts, + Opts.directory, + Opts.fail_under, + Opts.ignore_errors, + Opts.include, + Opts.omit, + Opts.precision, + Opts.show_contexts, + Opts.skip_covered, + Opts.no_skip_covered, + Opts.skip_empty, + Opts.title, + ] + GLOBAL_ARGS, + usage="[options] [modules]", + description=( + "Create an HTML report of the coverage of the files. " + "Each file gets its own page, with the source decorated to show " + "executed, excluded, and missed lines." + ), + ), + + 'json': CmdOptionParser( + "json", + [ + Opts.contexts, + Opts.fail_under, + Opts.ignore_errors, + Opts.include, + Opts.omit, + Opts.output_json, + Opts.json_pretty_print, + Opts.show_contexts, + ] + GLOBAL_ARGS, + usage="[options] [modules]", + description="Generate a JSON report of coverage results." + ), + + 'report': CmdOptionParser( + "report", + [ + Opts.contexts, + Opts.fail_under, + Opts.ignore_errors, + Opts.include, + Opts.omit, + Opts.precision, + Opts.sort, + Opts.show_missing, + Opts.skip_covered, + Opts.no_skip_covered, + Opts.skip_empty, + ] + GLOBAL_ARGS, + usage="[options] [modules]", + description="Report coverage statistics on modules." + ), + + 'run': CmdOptionParser( + "run", + [ + Opts.append, + Opts.branch, + Opts.concurrency, + Opts.context, + Opts.include, + Opts.module, + Opts.omit, + Opts.pylib, + Opts.parallel_mode, + Opts.source, + Opts.timid, + ] + GLOBAL_ARGS, + usage="[options] [program options]", + description="Run a Python program, measuring code execution." + ), + + 'xml': CmdOptionParser( + "xml", + [ + Opts.fail_under, + Opts.ignore_errors, + Opts.include, + Opts.omit, + Opts.output_xml, + Opts.skip_empty, + ] + GLOBAL_ARGS, + usage="[options] [modules]", + description="Generate an XML report of coverage results." + ), +} + + +def show_help(error=None, topic=None, parser=None): + """Display an error message, or the named topic.""" + assert error or topic or parser + + program_path = sys.argv[0] + if program_path.endswith(os.path.sep + '__main__.py'): + # The path is the main module of a package; get that path instead. + program_path = os.path.dirname(program_path) + program_name = os.path.basename(program_path) + if env.WINDOWS: + # entry_points={'console_scripts':...} on Windows makes files + # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These + # invoke coverage-script.py, coverage3-script.py, and + # coverage-3.5-script.py. argv[0] is the .py file, but we want to + # get back to the original form. + auto_suffix = "-script.py" + if program_name.endswith(auto_suffix): + program_name = program_name[:-len(auto_suffix)] + + help_params = dict(coverage.__dict__) + help_params['program_name'] = program_name + if CTracer is not None: + help_params['extension_modifier'] = 'with C extension' + else: + help_params['extension_modifier'] = 'without C extension' + + if error: + print(error, file=sys.stderr) + print("Use '%s help' for help." % (program_name,), file=sys.stderr) + elif parser: + print(parser.format_help().strip()) + print() + else: + help_msg = textwrap.dedent(HELP_TOPICS.get(topic, '')).strip() + if help_msg: + print(help_msg.format(**help_params)) + else: + print("Don't know topic %r" % topic) + print("Full documentation is at {__url__}".format(**help_params)) + + +OK, ERR, FAIL_UNDER = 0, 1, 2 + + +class CoverageScript(object): + """The command-line interface to coverage.py.""" + + def __init__(self): + self.global_option = False + self.coverage = None + + def command_line(self, argv): + """The bulk of the command line interface to coverage.py. + + `argv` is the argument list to process. + + Returns 0 if all is well, 1 if something went wrong. + + """ + # Collect the command-line options. + if not argv: + show_help(topic='minimum_help') + return OK + + # The command syntax we parse depends on the first argument. Global + # switch syntax always starts with an option. + self.global_option = argv[0].startswith('-') + if self.global_option: + parser = GlobalOptionParser() + else: + parser = CMDS.get(argv[0]) + if not parser: + show_help("Unknown command: '%s'" % argv[0]) + return ERR + argv = argv[1:] + + ok, options, args = parser.parse_args_ok(argv) + if not ok: + return ERR + + # Handle help and version. + if self.do_help(options, args, parser): + return OK + + # Listify the list options. + source = unshell_list(options.source) + omit = unshell_list(options.omit) + include = unshell_list(options.include) + debug = unshell_list(options.debug) + contexts = unshell_list(options.contexts) + + # Do something. + self.coverage = Coverage( + data_suffix=options.parallel_mode, + cover_pylib=options.pylib, + timid=options.timid, + branch=options.branch, + config_file=options.rcfile, + source=source, + omit=omit, + include=include, + debug=debug, + concurrency=options.concurrency, + check_preimported=True, + context=options.context, + ) + + if options.action == "debug": + return self.do_debug(args) + + elif options.action == "erase": + self.coverage.erase() + return OK + + elif options.action == "run": + return self.do_run(options, args) + + elif options.action == "combine": + if options.append: + self.coverage.load() + data_dirs = args or None + self.coverage.combine(data_dirs, strict=True) + self.coverage.save() + return OK + + # Remaining actions are reporting, with some common options. + report_args = dict( + morfs=unglob_args(args), + ignore_errors=options.ignore_errors, + omit=omit, + include=include, + contexts=contexts, + ) + + # We need to be able to import from the current directory, because + # plugins may try to, for example, to read Django settings. + sys.path.insert(0, '') + + self.coverage.load() + + total = None + if options.action == "report": + total = self.coverage.report( + show_missing=options.show_missing, + skip_covered=options.skip_covered, + skip_empty=options.skip_empty, + precision=options.precision, + sort=options.sort, + **report_args + ) + elif options.action == "annotate": + self.coverage.annotate(directory=options.directory, **report_args) + elif options.action == "html": + total = self.coverage.html_report( + directory=options.directory, + title=options.title, + skip_covered=options.skip_covered, + skip_empty=options.skip_empty, + show_contexts=options.show_contexts, + precision=options.precision, + **report_args + ) + elif options.action == "xml": + outfile = options.outfile + total = self.coverage.xml_report( + outfile=outfile, skip_empty=options.skip_empty, + **report_args + ) + elif options.action == "json": + outfile = options.outfile + total = self.coverage.json_report( + outfile=outfile, + pretty_print=options.pretty_print, + show_contexts=options.show_contexts, + **report_args + ) + + if total is not None: + # Apply the command line fail-under options, and then use the config + # value, so we can get fail_under from the config file. + if options.fail_under is not None: + self.coverage.set_option("report:fail_under", options.fail_under) + + fail_under = self.coverage.get_option("report:fail_under") + precision = self.coverage.get_option("report:precision") + if should_fail_under(total, fail_under, precision): + msg = "total of {total:.{p}f} is less than fail-under={fail_under:.{p}f}".format( + total=total, fail_under=fail_under, p=precision, + ) + print("Coverage failure:", msg) + return FAIL_UNDER + + return OK + + def do_help(self, options, args, parser): + """Deal with help requests. + + Return True if it handled the request, False if not. + + """ + # Handle help. + if options.help: + if self.global_option: + show_help(topic='help') + else: + show_help(parser=parser) + return True + + if options.action == "help": + if args: + for a in args: + parser = CMDS.get(a) + if parser: + show_help(parser=parser) + else: + show_help(topic=a) + else: + show_help(topic='help') + return True + + # Handle version. + if options.version: + show_help(topic='version') + return True + + return False + + def do_run(self, options, args): + """Implementation of 'coverage run'.""" + + if not args: + if options.module: + # Specified -m with nothing else. + show_help("No module specified for -m") + return ERR + command_line = self.coverage.get_option("run:command_line") + if command_line is not None: + args = shlex.split(command_line) + if args and args[0] == "-m": + options.module = True + args = args[1:] + if not args: + show_help("Nothing to do.") + return ERR + + if options.append and self.coverage.get_option("run:parallel"): + show_help("Can't append to data files in parallel mode.") + return ERR + + if options.concurrency == "multiprocessing": + # Can't set other run-affecting command line options with + # multiprocessing. + for opt_name in ['branch', 'include', 'omit', 'pylib', 'source', 'timid']: + # As it happens, all of these options have no default, meaning + # they will be None if they have not been specified. + if getattr(options, opt_name) is not None: + show_help( + "Options affecting multiprocessing must only be specified " + "in a configuration file.\n" + "Remove --{} from the command line.".format(opt_name) + ) + return ERR + + runner = PyRunner(args, as_module=bool(options.module)) + runner.prepare() + + if options.append: + self.coverage.load() + + # Run the script. + self.coverage.start() + code_ran = True + try: + runner.run() + except NoSource: + code_ran = False + raise + finally: + self.coverage.stop() + if code_ran: + self.coverage.save() + + return OK + + def do_debug(self, args): + """Implementation of 'coverage debug'.""" + + if not args: + show_help("What information would you like: config, data, sys, premain?") + return ERR + + for info in args: + if info == 'sys': + sys_info = self.coverage.sys_info() + print(info_header("sys")) + for line in info_formatter(sys_info): + print(" %s" % line) + elif info == 'data': + self.coverage.load() + data = self.coverage.get_data() + print(info_header("data")) + print("path: %s" % self.coverage.get_data().data_filename()) + if data: + print("has_arcs: %r" % data.has_arcs()) + summary = line_counts(data, fullpath=True) + filenames = sorted(summary.keys()) + print("\n%d files:" % len(filenames)) + for f in filenames: + line = "%s: %d lines" % (f, summary[f]) + plugin = data.file_tracer(f) + if plugin: + line += " [%s]" % plugin + print(line) + else: + print("No data collected") + elif info == 'config': + print(info_header("config")) + config_info = self.coverage.config.__dict__.items() + for line in info_formatter(config_info): + print(" %s" % line) + elif info == "premain": + print(info_header("premain")) + print(short_stack()) + else: + show_help("Don't know what you mean by %r" % info) + return ERR + + return OK + + +def unshell_list(s): + """Turn a command-line argument into a list.""" + if not s: + return None + if env.WINDOWS: + # When running coverage.py as coverage.exe, some of the behavior + # of the shell is emulated: wildcards are expanded into a list of + # file names. So you have to single-quote patterns on the command + # line, but (not) helpfully, the single quotes are included in the + # argument, so we have to strip them off here. + s = s.strip("'") + return s.split(',') + + +def unglob_args(args): + """Interpret shell wildcards for platforms that need it.""" + if env.WINDOWS: + globbed = [] + for arg in args: + if '?' in arg or '*' in arg: + globbed.extend(glob.glob(arg)) + else: + globbed.append(arg) + args = globbed + return args + + +HELP_TOPICS = { + 'help': """\ + Coverage.py, version {__version__} {extension_modifier} + Measure, collect, and report on code coverage in Python programs. + + usage: {program_name} [options] [args] + + Commands: + annotate Annotate source files with execution information. + combine Combine a number of data files. + debug Display information about the internals of coverage.py + erase Erase previously collected coverage data. + help Get help on using coverage.py. + html Create an HTML report. + json Create a JSON report of coverage results. + report Report coverage stats on modules. + run Run a Python program and measure code execution. + xml Create an XML report of coverage results. + + Use "{program_name} help " for detailed help on any command. + """, + + 'minimum_help': """\ + Code coverage for Python, version {__version__} {extension_modifier}. Use '{program_name} help' for help. + """, + + 'version': """\ + Coverage.py, version {__version__} {extension_modifier} + """, +} + + +def main(argv=None): + """The main entry point to coverage.py. + + This is installed as the script entry point. + + """ + if argv is None: + argv = sys.argv[1:] + try: + status = CoverageScript().command_line(argv) + except ExceptionDuringRun as err: + # An exception was caught while running the product code. The + # sys.exc_info() return tuple is packed into an ExceptionDuringRun + # exception. + traceback.print_exception(*err.args) # pylint: disable=no-value-for-parameter + status = ERR + except BaseCoverageException as err: + # A controlled error inside coverage.py: print the message to the user. + msg = err.args[0] + if env.PY2: + msg = msg.encode(output_encoding()) + print(msg) + status = ERR + except SystemExit as err: + # The user called `sys.exit()`. Exit with their argument, if any. + if err.args: + status = err.args[0] + else: + status = None + return status + +# Profiling using ox_profile. Install it from GitHub: +# pip install git+https://github.com/emin63/ox_profile.git +# +# $set_env.py: COVERAGE_PROFILE - Set to use ox_profile. +_profile = os.environ.get("COVERAGE_PROFILE", "") +if _profile: # pragma: debugging + from ox_profile.core.launchers import SimpleLauncher # pylint: disable=import-error + original_main = main + + def main(argv=None): # pylint: disable=function-redefined + """A wrapper around main that profiles.""" + profiler = SimpleLauncher.launch() + try: + return original_main(argv) + finally: + data, _ = profiler.query(re_filter='coverage', max_records=100) + print(profiler.show(query=data, limit=100, sep='', col='')) + profiler.cancel() diff --git a/robot/lib/python3.8/site-packages/coverage/collector.py b/robot/lib/python3.8/site-packages/coverage/collector.py new file mode 100644 index 0000000000000000000000000000000000000000..9333d66a827fe3ef780d908a7e91538cc13326f7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/collector.py @@ -0,0 +1,451 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Raw data collector for coverage.py.""" + +import os +import sys + +from coverage import env +from coverage.backward import litems, range # pylint: disable=redefined-builtin +from coverage.debug import short_stack +from coverage.disposition import FileDisposition +from coverage.misc import CoverageException, isolate_module +from coverage.pytracer import PyTracer + +os = isolate_module(os) + + +try: + # Use the C extension code when we can, for speed. + from coverage.tracer import CTracer, CFileDisposition +except ImportError: + # Couldn't import the C extension, maybe it isn't built. + if os.getenv('COVERAGE_TEST_TRACER') == 'c': + # During testing, we use the COVERAGE_TEST_TRACER environment variable + # to indicate that we've fiddled with the environment to test this + # fallback code. If we thought we had a C tracer, but couldn't import + # it, then exit quickly and clearly instead of dribbling confusing + # errors. I'm using sys.exit here instead of an exception because an + # exception here causes all sorts of other noise in unittest. + sys.stderr.write("*** COVERAGE_TEST_TRACER is 'c' but can't import CTracer!\n") + sys.exit(1) + CTracer = None + + +class Collector(object): + """Collects trace data. + + Creates a Tracer object for each thread, since they track stack + information. Each Tracer points to the same shared data, contributing + traced data points. + + When the Collector is started, it creates a Tracer for the current thread, + and installs a function to create Tracers for each new thread started. + When the Collector is stopped, all active Tracers are stopped. + + Threads started while the Collector is stopped will never have Tracers + associated with them. + + """ + + # The stack of active Collectors. Collectors are added here when started, + # and popped when stopped. Collectors on the stack are paused when not + # the top, and resumed when they become the top again. + _collectors = [] + + # The concurrency settings we support here. + SUPPORTED_CONCURRENCIES = set(["greenlet", "eventlet", "gevent", "thread"]) + + def __init__( + self, should_trace, check_include, should_start_context, file_mapper, + timid, branch, warn, concurrency, + ): + """Create a collector. + + `should_trace` is a function, taking a file name and a frame, and + returning a `coverage.FileDisposition object`. + + `check_include` is a function taking a file name and a frame. It returns + a boolean: True if the file should be traced, False if not. + + `should_start_context` is a function taking a frame, and returning a + string. If the frame should be the start of a new context, the string + is the new context. If the frame should not be the start of a new + context, return None. + + `file_mapper` is a function taking a filename, and returning a Unicode + filename. The result is the name that will be recorded in the data + file. + + If `timid` is true, then a slower simpler trace function will be + used. This is important for some environments where manipulation of + tracing functions make the faster more sophisticated trace function not + operate properly. + + If `branch` is true, then branches will be measured. This involves + collecting data on which statements followed each other (arcs). Use + `get_arc_data` to get the arc data. + + `warn` is a warning function, taking a single string message argument + and an optional slug argument which will be a string or None, to be + used if a warning needs to be issued. + + `concurrency` is a list of strings indicating the concurrency libraries + in use. Valid values are "greenlet", "eventlet", "gevent", or "thread" + (the default). Of these four values, only one can be supplied. Other + values are ignored. + + """ + self.should_trace = should_trace + self.check_include = check_include + self.should_start_context = should_start_context + self.file_mapper = file_mapper + self.warn = warn + self.branch = branch + self.threading = None + self.covdata = None + + self.static_context = None + + self.origin = short_stack() + + self.concur_id_func = None + self.mapped_file_cache = {} + + # We can handle a few concurrency options here, but only one at a time. + these_concurrencies = self.SUPPORTED_CONCURRENCIES.intersection(concurrency) + if len(these_concurrencies) > 1: + raise CoverageException("Conflicting concurrency settings: %s" % concurrency) + self.concurrency = these_concurrencies.pop() if these_concurrencies else '' + + try: + if self.concurrency == "greenlet": + import greenlet + self.concur_id_func = greenlet.getcurrent + elif self.concurrency == "eventlet": + import eventlet.greenthread # pylint: disable=import-error,useless-suppression + self.concur_id_func = eventlet.greenthread.getcurrent + elif self.concurrency == "gevent": + import gevent # pylint: disable=import-error,useless-suppression + self.concur_id_func = gevent.getcurrent + elif self.concurrency == "thread" or not self.concurrency: + # It's important to import threading only if we need it. If + # it's imported early, and the program being measured uses + # gevent, then gevent's monkey-patching won't work properly. + import threading + self.threading = threading + else: + raise CoverageException("Don't understand concurrency=%s" % concurrency) + except ImportError: + raise CoverageException( + "Couldn't trace with concurrency=%s, the module isn't installed." % ( + self.concurrency, + ) + ) + + self.reset() + + if timid: + # Being timid: use the simple Python trace function. + self._trace_class = PyTracer + else: + # Being fast: use the C Tracer if it is available, else the Python + # trace function. + self._trace_class = CTracer or PyTracer + + if self._trace_class is CTracer: + self.file_disposition_class = CFileDisposition + self.supports_plugins = True + else: + self.file_disposition_class = FileDisposition + self.supports_plugins = False + + def __repr__(self): + return "" % (id(self), self.tracer_name()) + + def use_data(self, covdata, context): + """Use `covdata` for recording data.""" + self.covdata = covdata + self.static_context = context + self.covdata.set_context(self.static_context) + + def tracer_name(self): + """Return the class name of the tracer we're using.""" + return self._trace_class.__name__ + + def _clear_data(self): + """Clear out existing data, but stay ready for more collection.""" + # We used to used self.data.clear(), but that would remove filename + # keys and data values that were still in use higher up the stack + # when we are called as part of switch_context. + for d in self.data.values(): + d.clear() + + for tracer in self.tracers: + tracer.reset_activity() + + def reset(self): + """Clear collected data, and prepare to collect more.""" + # A dictionary mapping file names to dicts with line number keys (if not + # branch coverage), or mapping file names to dicts with line number + # pairs as keys (if branch coverage). + self.data = {} + + # A dictionary mapping file names to file tracer plugin names that will + # handle them. + self.file_tracers = {} + + self.disabled_plugins = set() + + # The .should_trace_cache attribute is a cache from file names to + # coverage.FileDisposition objects, or None. When a file is first + # considered for tracing, a FileDisposition is obtained from + # Coverage.should_trace. Its .trace attribute indicates whether the + # file should be traced or not. If it should be, a plugin with dynamic + # file names can decide not to trace it based on the dynamic file name + # being excluded by the inclusion rules, in which case the + # FileDisposition will be replaced by None in the cache. + if env.PYPY: + import __pypy__ # pylint: disable=import-error + # Alex Gaynor said: + # should_trace_cache is a strictly growing key: once a key is in + # it, it never changes. Further, the keys used to access it are + # generally constant, given sufficient context. That is to say, at + # any given point _trace() is called, pypy is able to know the key. + # This is because the key is determined by the physical source code + # line, and that's invariant with the call site. + # + # This property of a dict with immutable keys, combined with + # call-site-constant keys is a match for PyPy's module dict, + # which is optimized for such workloads. + # + # This gives a 20% benefit on the workload described at + # https://bitbucket.org/pypy/pypy/issue/1871/10x-slower-than-cpython-under-coverage + self.should_trace_cache = __pypy__.newdict("module") + else: + self.should_trace_cache = {} + + # Our active Tracers. + self.tracers = [] + + self._clear_data() + + def _start_tracer(self): + """Start a new Tracer object, and store it in self.tracers.""" + tracer = self._trace_class() + tracer.data = self.data + tracer.trace_arcs = self.branch + tracer.should_trace = self.should_trace + tracer.should_trace_cache = self.should_trace_cache + tracer.warn = self.warn + + if hasattr(tracer, 'concur_id_func'): + tracer.concur_id_func = self.concur_id_func + elif self.concur_id_func: + raise CoverageException( + "Can't support concurrency=%s with %s, only threads are supported" % ( + self.concurrency, self.tracer_name(), + ) + ) + + if hasattr(tracer, 'file_tracers'): + tracer.file_tracers = self.file_tracers + if hasattr(tracer, 'threading'): + tracer.threading = self.threading + if hasattr(tracer, 'check_include'): + tracer.check_include = self.check_include + if hasattr(tracer, 'should_start_context'): + tracer.should_start_context = self.should_start_context + tracer.switch_context = self.switch_context + if hasattr(tracer, 'disable_plugin'): + tracer.disable_plugin = self.disable_plugin + + fn = tracer.start() + self.tracers.append(tracer) + + return fn + + # The trace function has to be set individually on each thread before + # execution begins. Ironically, the only support the threading module has + # for running code before the thread main is the tracing function. So we + # install this as a trace function, and the first time it's called, it does + # the real trace installation. + + def _installation_trace(self, frame, event, arg): + """Called on new threads, installs the real tracer.""" + # Remove ourselves as the trace function. + sys.settrace(None) + # Install the real tracer. + fn = self._start_tracer() + # Invoke the real trace function with the current event, to be sure + # not to lose an event. + if fn: + fn = fn(frame, event, arg) + # Return the new trace function to continue tracing in this scope. + return fn + + def start(self): + """Start collecting trace information.""" + if self._collectors: + self._collectors[-1].pause() + + self.tracers = [] + + # Check to see whether we had a fullcoverage tracer installed. If so, + # get the stack frames it stashed away for us. + traces0 = [] + fn0 = sys.gettrace() + if fn0: + tracer0 = getattr(fn0, '__self__', None) + if tracer0: + traces0 = getattr(tracer0, 'traces', []) + + try: + # Install the tracer on this thread. + fn = self._start_tracer() + except: + if self._collectors: + self._collectors[-1].resume() + raise + + # If _start_tracer succeeded, then we add ourselves to the global + # stack of collectors. + self._collectors.append(self) + + # Replay all the events from fullcoverage into the new trace function. + for args in traces0: + (frame, event, arg), lineno = args + try: + fn(frame, event, arg, lineno=lineno) + except TypeError: + raise Exception("fullcoverage must be run with the C trace function.") + + # Install our installation tracer in threading, to jump-start other + # threads. + if self.threading: + self.threading.settrace(self._installation_trace) + + def stop(self): + """Stop collecting trace information.""" + assert self._collectors + if self._collectors[-1] is not self: + print("self._collectors:") + for c in self._collectors: + print(" {!r}\n{}".format(c, c.origin)) + assert self._collectors[-1] is self, ( + "Expected current collector to be %r, but it's %r" % (self, self._collectors[-1]) + ) + + self.pause() + + # Remove this Collector from the stack, and resume the one underneath + # (if any). + self._collectors.pop() + if self._collectors: + self._collectors[-1].resume() + + def pause(self): + """Pause tracing, but be prepared to `resume`.""" + for tracer in self.tracers: + tracer.stop() + stats = tracer.get_stats() + if stats: + print("\nCoverage.py tracer stats:") + for k in sorted(stats.keys()): + print("%20s: %s" % (k, stats[k])) + if self.threading: + self.threading.settrace(None) + + def resume(self): + """Resume tracing after a `pause`.""" + for tracer in self.tracers: + tracer.start() + if self.threading: + self.threading.settrace(self._installation_trace) + else: + self._start_tracer() + + def _activity(self): + """Has any activity been traced? + + Returns a boolean, True if any trace function was invoked. + + """ + return any(tracer.activity() for tracer in self.tracers) + + def switch_context(self, new_context): + """Switch to a new dynamic context.""" + self.flush_data() + if self.static_context: + context = self.static_context + if new_context: + context += "|" + new_context + else: + context = new_context + self.covdata.set_context(context) + + def disable_plugin(self, disposition): + """Disable the plugin mentioned in `disposition`.""" + file_tracer = disposition.file_tracer + plugin = file_tracer._coverage_plugin + plugin_name = plugin._coverage_plugin_name + self.warn("Disabling plug-in {!r} due to previous exception".format(plugin_name)) + plugin._coverage_enabled = False + disposition.trace = False + + def cached_mapped_file(self, filename): + """A locally cached version of file names mapped through file_mapper.""" + key = (type(filename), filename) + try: + return self.mapped_file_cache[key] + except KeyError: + return self.mapped_file_cache.setdefault(key, self.file_mapper(filename)) + + def mapped_file_dict(self, d): + """Return a dict like d, but with keys modified by file_mapper.""" + # The call to litems() ensures that the GIL protects the dictionary + # iterator against concurrent modifications by tracers running + # in other threads. We try three times in case of concurrent + # access, hoping to get a clean copy. + runtime_err = None + for _ in range(3): + try: + items = litems(d) + except RuntimeError as ex: + runtime_err = ex + else: + break + else: + raise runtime_err + + return dict((self.cached_mapped_file(k), v) for k, v in items if v) + + def plugin_was_disabled(self, plugin): + """Record that `plugin` was disabled during the run.""" + self.disabled_plugins.add(plugin._coverage_plugin_name) + + def flush_data(self): + """Save the collected data to our associated `CoverageData`. + + Data may have also been saved along the way. This forces the + last of the data to be saved. + + Returns True if there was data to save, False if not. + """ + if not self._activity(): + return False + + if self.branch: + self.covdata.add_arcs(self.mapped_file_dict(self.data)) + else: + self.covdata.add_lines(self.mapped_file_dict(self.data)) + + file_tracers = { + k: v for k, v in self.file_tracers.items() + if v not in self.disabled_plugins + } + self.covdata.add_file_tracers(self.mapped_file_dict(file_tracers)) + + self._clear_data() + return True diff --git a/robot/lib/python3.8/site-packages/coverage/config.py b/robot/lib/python3.8/site-packages/coverage/config.py new file mode 100644 index 0000000000000000000000000000000000000000..2af4a1cc81ae04593dc855a98d05d390432bcee0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/config.py @@ -0,0 +1,558 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Config file for coverage.py""" + +import collections +import copy +import os +import os.path +import re + +from coverage import env +from coverage.backward import configparser, iitems, string_class +from coverage.misc import contract, CoverageException, isolate_module +from coverage.misc import substitute_variables + +from coverage.tomlconfig import TomlConfigParser, TomlDecodeError + +os = isolate_module(os) + + +class HandyConfigParser(configparser.RawConfigParser): + """Our specialization of ConfigParser.""" + + def __init__(self, our_file): + """Create the HandyConfigParser. + + `our_file` is True if this config file is specifically for coverage, + False if we are examining another config file (tox.ini, setup.cfg) + for possible settings. + """ + + configparser.RawConfigParser.__init__(self) + self.section_prefixes = ["coverage:"] + if our_file: + self.section_prefixes.append("") + + def read(self, filenames, encoding=None): + """Read a file name as UTF-8 configuration data.""" + kwargs = {} + if env.PYVERSION >= (3, 2): + kwargs['encoding'] = encoding or "utf-8" + return configparser.RawConfigParser.read(self, filenames, **kwargs) + + def has_option(self, section, option): + for section_prefix in self.section_prefixes: + real_section = section_prefix + section + has = configparser.RawConfigParser.has_option(self, real_section, option) + if has: + return has + return False + + def has_section(self, section): + for section_prefix in self.section_prefixes: + real_section = section_prefix + section + has = configparser.RawConfigParser.has_section(self, real_section) + if has: + return real_section + return False + + def options(self, section): + for section_prefix in self.section_prefixes: + real_section = section_prefix + section + if configparser.RawConfigParser.has_section(self, real_section): + return configparser.RawConfigParser.options(self, real_section) + raise configparser.NoSectionError + + def get_section(self, section): + """Get the contents of a section, as a dictionary.""" + d = {} + for opt in self.options(section): + d[opt] = self.get(section, opt) + return d + + def get(self, section, option, *args, **kwargs): + """Get a value, replacing environment variables also. + + The arguments are the same as `RawConfigParser.get`, but in the found + value, ``$WORD`` or ``${WORD}`` are replaced by the value of the + environment variable ``WORD``. + + Returns the finished value. + + """ + for section_prefix in self.section_prefixes: + real_section = section_prefix + section + if configparser.RawConfigParser.has_option(self, real_section, option): + break + else: + raise configparser.NoOptionError + + v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs) + v = substitute_variables(v, os.environ) + return v + + def getlist(self, section, option): + """Read a list of strings. + + The value of `section` and `option` is treated as a comma- and newline- + separated list of strings. Each value is stripped of whitespace. + + Returns the list of strings. + + """ + value_list = self.get(section, option) + values = [] + for value_line in value_list.split('\n'): + for value in value_line.split(','): + value = value.strip() + if value: + values.append(value) + return values + + def getregexlist(self, section, option): + """Read a list of full-line regexes. + + The value of `section` and `option` is treated as a newline-separated + list of regexes. Each value is stripped of whitespace. + + Returns the list of strings. + + """ + line_list = self.get(section, option) + value_list = [] + for value in line_list.splitlines(): + value = value.strip() + try: + re.compile(value) + except re.error as e: + raise CoverageException( + "Invalid [%s].%s value %r: %s" % (section, option, value, e) + ) + if value: + value_list.append(value) + return value_list + + +# The default line exclusion regexes. +DEFAULT_EXCLUDE = [ + r'#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(cover|COVER)', +] + +# The default partial branch regexes, to be modified by the user. +DEFAULT_PARTIAL = [ + r'#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(branch|BRANCH)', +] + +# The default partial branch regexes, based on Python semantics. +# These are any Python branching constructs that can't actually execute all +# their branches. +DEFAULT_PARTIAL_ALWAYS = [ + 'while (True|1|False|0):', + 'if (True|1|False|0):', +] + + +class CoverageConfig(object): + """Coverage.py configuration. + + The attributes of this class are the various settings that control the + operation of coverage.py. + + """ + # pylint: disable=too-many-instance-attributes + + def __init__(self): + """Initialize the configuration attributes to their defaults.""" + # Metadata about the config. + # We tried to read these config files. + self.attempted_config_files = [] + # We did read these config files, but maybe didn't find any content for us. + self.config_files_read = [] + # The file that gave us our configuration. + self.config_file = None + self._config_contents = None + + # Defaults for [run] and [report] + self._include = None + self._omit = None + + # Defaults for [run] + self.branch = False + self.command_line = None + self.concurrency = None + self.context = None + self.cover_pylib = False + self.data_file = ".coverage" + self.debug = [] + self.disable_warnings = [] + self.dynamic_context = None + self.note = None + self.parallel = False + self.plugins = [] + self.relative_files = False + self.run_include = None + self.run_omit = None + self.source = None + self.source_pkgs = [] + self.timid = False + self._crash = None + + # Defaults for [report] + self.exclude_list = DEFAULT_EXCLUDE[:] + self.fail_under = 0.0 + self.ignore_errors = False + self.report_include = None + self.report_omit = None + self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:] + self.partial_list = DEFAULT_PARTIAL[:] + self.precision = 0 + self.report_contexts = None + self.show_missing = False + self.skip_covered = False + self.skip_empty = False + self.sort = None + + # Defaults for [html] + self.extra_css = None + self.html_dir = "htmlcov" + self.html_title = "Coverage report" + self.show_contexts = False + + # Defaults for [xml] + self.xml_output = "coverage.xml" + self.xml_package_depth = 99 + + # Defaults for [json] + self.json_output = "coverage.json" + self.json_pretty_print = False + self.json_show_contexts = False + + # Defaults for [paths] + self.paths = collections.OrderedDict() + + # Options for plugins + self.plugin_options = {} + + MUST_BE_LIST = [ + "debug", "concurrency", "plugins", + "report_omit", "report_include", + "run_omit", "run_include", + ] + + def from_args(self, **kwargs): + """Read config values from `kwargs`.""" + for k, v in iitems(kwargs): + if v is not None: + if k in self.MUST_BE_LIST and isinstance(v, string_class): + v = [v] + setattr(self, k, v) + + @contract(filename=str) + def from_file(self, filename, our_file): + """Read configuration from a .rc file. + + `filename` is a file name to read. + + `our_file` is True if this config file is specifically for coverage, + False if we are examining another config file (tox.ini, setup.cfg) + for possible settings. + + Returns True or False, whether the file could be read, and it had some + coverage.py settings in it. + + """ + _, ext = os.path.splitext(filename) + if ext == '.toml': + cp = TomlConfigParser(our_file) + else: + cp = HandyConfigParser(our_file) + + self.attempted_config_files.append(filename) + + try: + files_read = cp.read(filename) + except (configparser.Error, TomlDecodeError) as err: + raise CoverageException("Couldn't read config file %s: %s" % (filename, err)) + if not files_read: + return False + + self.config_files_read.extend(map(os.path.abspath, files_read)) + + any_set = False + try: + for option_spec in self.CONFIG_FILE_OPTIONS: + was_set = self._set_attr_from_config_option(cp, *option_spec) + if was_set: + any_set = True + except ValueError as err: + raise CoverageException("Couldn't read config file %s: %s" % (filename, err)) + + # Check that there are no unrecognized options. + all_options = collections.defaultdict(set) + for option_spec in self.CONFIG_FILE_OPTIONS: + section, option = option_spec[1].split(":") + all_options[section].add(option) + + for section, options in iitems(all_options): + real_section = cp.has_section(section) + if real_section: + for unknown in set(cp.options(section)) - options: + raise CoverageException( + "Unrecognized option '[%s] %s=' in config file %s" % ( + real_section, unknown, filename + ) + ) + + # [paths] is special + if cp.has_section('paths'): + for option in cp.options('paths'): + self.paths[option] = cp.getlist('paths', option) + any_set = True + + # plugins can have options + for plugin in self.plugins: + if cp.has_section(plugin): + self.plugin_options[plugin] = cp.get_section(plugin) + any_set = True + + # Was this file used as a config file? If it's specifically our file, + # then it was used. If we're piggybacking on someone else's file, + # then it was only used if we found some settings in it. + if our_file: + used = True + else: + used = any_set + + if used: + self.config_file = os.path.abspath(filename) + with open(filename, "rb") as f: + self._config_contents = f.read() + + return used + + def copy(self): + """Return a copy of the configuration.""" + return copy.deepcopy(self) + + CONFIG_FILE_OPTIONS = [ + # These are *args for _set_attr_from_config_option: + # (attr, where, type_="") + # + # attr is the attribute to set on the CoverageConfig object. + # where is the section:name to read from the configuration file. + # type_ is the optional type to apply, by using .getTYPE to read the + # configuration value from the file. + + # [run] + ('branch', 'run:branch', 'boolean'), + ('command_line', 'run:command_line'), + ('concurrency', 'run:concurrency', 'list'), + ('context', 'run:context'), + ('cover_pylib', 'run:cover_pylib', 'boolean'), + ('data_file', 'run:data_file'), + ('debug', 'run:debug', 'list'), + ('disable_warnings', 'run:disable_warnings', 'list'), + ('dynamic_context', 'run:dynamic_context'), + ('note', 'run:note'), + ('parallel', 'run:parallel', 'boolean'), + ('plugins', 'run:plugins', 'list'), + ('relative_files', 'run:relative_files', 'boolean'), + ('run_include', 'run:include', 'list'), + ('run_omit', 'run:omit', 'list'), + ('source', 'run:source', 'list'), + ('source_pkgs', 'run:source_pkgs', 'list'), + ('timid', 'run:timid', 'boolean'), + ('_crash', 'run:_crash'), + + # [report] + ('exclude_list', 'report:exclude_lines', 'regexlist'), + ('fail_under', 'report:fail_under', 'float'), + ('ignore_errors', 'report:ignore_errors', 'boolean'), + ('partial_always_list', 'report:partial_branches_always', 'regexlist'), + ('partial_list', 'report:partial_branches', 'regexlist'), + ('precision', 'report:precision', 'int'), + ('report_contexts', 'report:contexts', 'list'), + ('report_include', 'report:include', 'list'), + ('report_omit', 'report:omit', 'list'), + ('show_missing', 'report:show_missing', 'boolean'), + ('skip_covered', 'report:skip_covered', 'boolean'), + ('skip_empty', 'report:skip_empty', 'boolean'), + ('sort', 'report:sort'), + + # [html] + ('extra_css', 'html:extra_css'), + ('html_dir', 'html:directory'), + ('html_title', 'html:title'), + ('show_contexts', 'html:show_contexts', 'boolean'), + + # [xml] + ('xml_output', 'xml:output'), + ('xml_package_depth', 'xml:package_depth', 'int'), + + # [json] + ('json_output', 'json:output'), + ('json_pretty_print', 'json:pretty_print', 'boolean'), + ('json_show_contexts', 'json:show_contexts', 'boolean'), + ] + + def _set_attr_from_config_option(self, cp, attr, where, type_=''): + """Set an attribute on self if it exists in the ConfigParser. + + Returns True if the attribute was set. + + """ + section, option = where.split(":") + if cp.has_option(section, option): + method = getattr(cp, 'get' + type_) + setattr(self, attr, method(section, option)) + return True + return False + + def get_plugin_options(self, plugin): + """Get a dictionary of options for the plugin named `plugin`.""" + return self.plugin_options.get(plugin, {}) + + def set_option(self, option_name, value): + """Set an option in the configuration. + + `option_name` is a colon-separated string indicating the section and + option name. For example, the ``branch`` option in the ``[run]`` + section of the config file would be indicated with `"run:branch"`. + + `value` is the new value for the option. + + """ + # Special-cased options. + if option_name == "paths": + self.paths = value + return + + # Check all the hard-coded options. + for option_spec in self.CONFIG_FILE_OPTIONS: + attr, where = option_spec[:2] + if where == option_name: + setattr(self, attr, value) + return + + # See if it's a plugin option. + plugin_name, _, key = option_name.partition(":") + if key and plugin_name in self.plugins: + self.plugin_options.setdefault(plugin_name, {})[key] = value + return + + # If we get here, we didn't find the option. + raise CoverageException("No such option: %r" % option_name) + + def get_option(self, option_name): + """Get an option from the configuration. + + `option_name` is a colon-separated string indicating the section and + option name. For example, the ``branch`` option in the ``[run]`` + section of the config file would be indicated with `"run:branch"`. + + Returns the value of the option. + + """ + # Special-cased options. + if option_name == "paths": + return self.paths + + # Check all the hard-coded options. + for option_spec in self.CONFIG_FILE_OPTIONS: + attr, where = option_spec[:2] + if where == option_name: + return getattr(self, attr) + + # See if it's a plugin option. + plugin_name, _, key = option_name.partition(":") + if key and plugin_name in self.plugins: + return self.plugin_options.get(plugin_name, {}).get(key) + + # If we get here, we didn't find the option. + raise CoverageException("No such option: %r" % option_name) + + +def config_files_to_try(config_file): + """What config files should we try to read? + + Returns a list of tuples: + (filename, is_our_file, was_file_specified) + """ + + # Some API users were specifying ".coveragerc" to mean the same as + # True, so make it so. + if config_file == ".coveragerc": + config_file = True + specified_file = (config_file is not True) + if not specified_file: + # No file was specified. Check COVERAGE_RCFILE. + config_file = os.environ.get('COVERAGE_RCFILE') + if config_file: + specified_file = True + if not specified_file: + # Still no file specified. Default to .coveragerc + config_file = ".coveragerc" + files_to_try = [ + (config_file, True, specified_file), + ("setup.cfg", False, False), + ("tox.ini", False, False), + ("pyproject.toml", False, False), + ] + return files_to_try + + +def read_coverage_config(config_file, **kwargs): + """Read the coverage.py configuration. + + Arguments: + config_file: a boolean or string, see the `Coverage` class for the + tricky details. + all others: keyword arguments from the `Coverage` class, used for + setting values in the configuration. + + Returns: + config: + config is a CoverageConfig object read from the appropriate + configuration file. + + """ + # Build the configuration from a number of sources: + # 1) defaults: + config = CoverageConfig() + + # 2) from a file: + if config_file: + files_to_try = config_files_to_try(config_file) + + for fname, our_file, specified_file in files_to_try: + config_read = config.from_file(fname, our_file=our_file) + if config_read: + break + if specified_file: + raise CoverageException("Couldn't read '%s' as a config file" % fname) + + # $set_env.py: COVERAGE_DEBUG - Options for --debug. + # 3) from environment variables: + env_data_file = os.environ.get('COVERAGE_FILE') + if env_data_file: + config.data_file = env_data_file + debugs = os.environ.get('COVERAGE_DEBUG') + if debugs: + config.debug.extend(d.strip() for d in debugs.split(",")) + + # 4) from constructor arguments: + config.from_args(**kwargs) + + # Once all the config has been collected, there's a little post-processing + # to do. + config.data_file = os.path.expanduser(config.data_file) + config.html_dir = os.path.expanduser(config.html_dir) + config.xml_output = os.path.expanduser(config.xml_output) + config.paths = collections.OrderedDict( + (k, [os.path.expanduser(f) for f in v]) + for k, v in config.paths.items() + ) + + return config diff --git a/robot/lib/python3.8/site-packages/coverage/context.py b/robot/lib/python3.8/site-packages/coverage/context.py new file mode 100644 index 0000000000000000000000000000000000000000..ea13da21edd24ce867549a1d1fa7a1893e9db339 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/context.py @@ -0,0 +1,91 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Determine contexts for coverage.py""" + + +def combine_context_switchers(context_switchers): + """Create a single context switcher from multiple switchers. + + `context_switchers` is a list of functions that take a frame as an + argument and return a string to use as the new context label. + + Returns a function that composites `context_switchers` functions, or None + if `context_switchers` is an empty list. + + When invoked, the combined switcher calls `context_switchers` one-by-one + until a string is returned. The combined switcher returns None if all + `context_switchers` return None. + """ + if not context_switchers: + return None + + if len(context_switchers) == 1: + return context_switchers[0] + + def should_start_context(frame): + """The combiner for multiple context switchers.""" + for switcher in context_switchers: + new_context = switcher(frame) + if new_context is not None: + return new_context + return None + + return should_start_context + + +def should_start_context_test_function(frame): + """Is this frame calling a test_* function?""" + co_name = frame.f_code.co_name + if co_name.startswith("test") or co_name == "runTest": + return qualname_from_frame(frame) + return None + + +def qualname_from_frame(frame): + """Get a qualified name for the code running in `frame`.""" + co = frame.f_code + fname = co.co_name + method = None + if co.co_argcount and co.co_varnames[0] == "self": + self = frame.f_locals["self"] + method = getattr(self, fname, None) + + if method is None: + func = frame.f_globals.get(fname) + if func is None: + return None + return func.__module__ + '.' + fname + + func = getattr(method, '__func__', None) + if func is None: + cls = self.__class__ + return cls.__module__ + '.' + cls.__name__ + "." + fname + + if hasattr(func, '__qualname__'): + qname = func.__module__ + '.' + func.__qualname__ + else: + for cls in getattr(self.__class__, '__mro__', ()): + f = cls.__dict__.get(fname, None) + if f is None: + continue + if f is func: + qname = cls.__module__ + '.' + cls.__name__ + "." + fname + break + else: + # Support for old-style classes. + def mro(bases): + for base in bases: + f = base.__dict__.get(fname, None) + if f is func: + return base.__module__ + '.' + base.__name__ + "." + fname + for base in bases: + qname = mro(base.__bases__) + if qname is not None: + return qname + return None + qname = mro([self.__class__]) + if qname is None: + qname = func.__module__ + '.' + fname + + return qname diff --git a/robot/lib/python3.8/site-packages/coverage/control.py b/robot/lib/python3.8/site-packages/coverage/control.py new file mode 100644 index 0000000000000000000000000000000000000000..086490730124ce9f4a5149fe9088f83e9994fff3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/control.py @@ -0,0 +1,1135 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Core control stuff for coverage.py.""" + +import atexit +import collections +import contextlib +import os +import os.path +import platform +import sys +import time + +from coverage import env +from coverage.annotate import AnnotateReporter +from coverage.backward import string_class, iitems +from coverage.collector import Collector, CTracer +from coverage.config import read_coverage_config +from coverage.context import should_start_context_test_function, combine_context_switchers +from coverage.data import CoverageData, combine_parallel_data +from coverage.debug import DebugControl, short_stack, write_formatted_info +from coverage.disposition import disposition_debug_msg +from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory +from coverage.html import HtmlReporter +from coverage.inorout import InOrOut +from coverage.jsonreport import JsonReporter +from coverage.misc import CoverageException, bool_or_none, join_regex +from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module +from coverage.plugin import FileReporter +from coverage.plugin_support import Plugins +from coverage.python import PythonFileReporter +from coverage.report import render_report +from coverage.results import Analysis, Numbers +from coverage.summary import SummaryReporter +from coverage.xmlreport import XmlReporter + +try: + from coverage.multiproc import patch_multiprocessing +except ImportError: # pragma: only jython + # Jython has no multiprocessing module. + patch_multiprocessing = None + +os = isolate_module(os) + +@contextlib.contextmanager +def override_config(cov, **kwargs): + """Temporarily tweak the configuration of `cov`. + + The arguments are applied to `cov.config` with the `from_args` method. + At the end of the with-statement, the old configuration is restored. + """ + original_config = cov.config + cov.config = cov.config.copy() + try: + cov.config.from_args(**kwargs) + yield + finally: + cov.config = original_config + + +_DEFAULT_DATAFILE = DefaultValue("MISSING") + +class Coverage(object): + """Programmatic access to coverage.py. + + To use:: + + from coverage import Coverage + + cov = Coverage() + cov.start() + #.. call your code .. + cov.stop() + cov.html_report(directory='covhtml') + + Note: in keeping with Python custom, names starting with underscore are + not part of the public API. They might stop working at any point. Please + limit yourself to documented methods to avoid problems. + + """ + + # The stack of started Coverage instances. + _instances = [] + + @classmethod + def current(cls): + """Get the latest started `Coverage` instance, if any. + + Returns: a `Coverage` instance, or None. + + .. versionadded:: 5.0 + + """ + if cls._instances: + return cls._instances[-1] + else: + return None + + def __init__( + self, data_file=_DEFAULT_DATAFILE, data_suffix=None, cover_pylib=None, + auto_data=False, timid=None, branch=None, config_file=True, + source=None, source_pkgs=None, omit=None, include=None, debug=None, + concurrency=None, check_preimported=False, context=None, + ): # pylint: disable=too-many-arguments + """ + Many of these arguments duplicate and override values that can be + provided in a configuration file. Parameters that are missing here + will use values from the config file. + + `data_file` is the base name of the data file to use. The config value + defaults to ".coverage". None can be provided to prevent writing a data + file. `data_suffix` is appended (with a dot) to `data_file` to create + the final file name. If `data_suffix` is simply True, then a suffix is + created with the machine and process identity included. + + `cover_pylib` is a boolean determining whether Python code installed + with the Python interpreter is measured. This includes the Python + standard library and any packages installed with the interpreter. + + If `auto_data` is true, then any existing data file will be read when + coverage measurement starts, and data will be saved automatically when + measurement stops. + + If `timid` is true, then a slower and simpler trace function will be + used. This is important for some environments where manipulation of + tracing functions breaks the faster trace function. + + If `branch` is true, then branch coverage will be measured in addition + to the usual statement coverage. + + `config_file` determines what configuration file to read: + + * If it is ".coveragerc", it is interpreted as if it were True, + for backward compatibility. + + * If it is a string, it is the name of the file to read. If the + file can't be read, it is an error. + + * If it is True, then a few standard files names are tried + (".coveragerc", "setup.cfg", "tox.ini"). It is not an error for + these files to not be found. + + * If it is False, then no configuration file is read. + + `source` is a list of file paths or package names. Only code located + in the trees indicated by the file paths or package names will be + measured. + + `source_pkgs` is a list of package names. It works the same as + `source`, but can be used to name packages where the name can also be + interpreted as a file path. + + `include` and `omit` are lists of file name patterns. Files that match + `include` will be measured, files that match `omit` will not. Each + will also accept a single string argument. + + `debug` is a list of strings indicating what debugging information is + desired. + + `concurrency` is a string indicating the concurrency library being used + in the measured code. Without this, coverage.py will get incorrect + results if these libraries are in use. Valid strings are "greenlet", + "eventlet", "gevent", "multiprocessing", or "thread" (the default). + This can also be a list of these strings. + + If `check_preimported` is true, then when coverage is started, the + already-imported files will be checked to see if they should be + measured by coverage. Importing measured files before coverage is + started can mean that code is missed. + + `context` is a string to use as the :ref:`static context + ` label for collected data. + + .. versionadded:: 4.0 + The `concurrency` parameter. + + .. versionadded:: 4.2 + The `concurrency` parameter can now be a list of strings. + + .. versionadded:: 5.0 + The `check_preimported` and `context` parameters. + + .. versionadded:: 5.3 + The `source_pkgs` parameter. + + """ + # data_file=None means no disk file at all. data_file missing means + # use the value from the config file. + self._no_disk = data_file is None + if data_file is _DEFAULT_DATAFILE: + data_file = None + + # Build our configuration from a number of sources. + self.config = read_coverage_config( + config_file=config_file, + data_file=data_file, cover_pylib=cover_pylib, timid=timid, + branch=branch, parallel=bool_or_none(data_suffix), + source=source, source_pkgs=source_pkgs, run_omit=omit, run_include=include, debug=debug, + report_omit=omit, report_include=include, + concurrency=concurrency, context=context, + ) + + # This is injectable by tests. + self._debug_file = None + + self._auto_load = self._auto_save = auto_data + self._data_suffix_specified = data_suffix + + # Is it ok for no data to be collected? + self._warn_no_data = True + self._warn_unimported_source = True + self._warn_preimported_source = check_preimported + self._no_warn_slugs = None + + # A record of all the warnings that have been issued. + self._warnings = [] + + # Other instance attributes, set later. + self._data = self._collector = None + self._plugins = None + self._inorout = None + self._data_suffix = self._run_suffix = None + self._exclude_re = None + self._debug = None + self._file_mapper = None + + # State machine variables: + # Have we initialized everything? + self._inited = False + self._inited_for_start = False + # Have we started collecting and not stopped it? + self._started = False + # Should we write the debug output? + self._should_write_debug = True + + # If we have sub-process measurement happening automatically, then we + # want any explicit creation of a Coverage object to mean, this process + # is already coverage-aware, so don't auto-measure it. By now, the + # auto-creation of a Coverage object has already happened. But we can + # find it and tell it not to save its data. + if not env.METACOV: + _prevent_sub_process_measurement() + + def _init(self): + """Set all the initial state. + + This is called by the public methods to initialize state. This lets us + construct a :class:`Coverage` object, then tweak its state before this + function is called. + + """ + if self._inited: + return + + self._inited = True + + # Create and configure the debugging controller. COVERAGE_DEBUG_FILE + # is an environment variable, the name of a file to append debug logs + # to. + self._debug = DebugControl(self.config.debug, self._debug_file) + + if "multiprocessing" in (self.config.concurrency or ()): + # Multi-processing uses parallel for the subprocesses, so also use + # it for the main process. + self.config.parallel = True + + # _exclude_re is a dict that maps exclusion list names to compiled regexes. + self._exclude_re = {} + + set_relative_directory() + self._file_mapper = relative_filename if self.config.relative_files else abs_file + + # Load plugins + self._plugins = Plugins.load_plugins(self.config.plugins, self.config, self._debug) + + # Run configuring plugins. + for plugin in self._plugins.configurers: + # We need an object with set_option and get_option. Either self or + # self.config will do. Choosing randomly stops people from doing + # other things with those objects, against the public API. Yes, + # this is a bit childish. :) + plugin.configure([self, self.config][int(time.time()) % 2]) + + def _post_init(self): + """Stuff to do after everything is initialized.""" + if self._should_write_debug: + self._should_write_debug = False + self._write_startup_debug() + + # '[run] _crash' will raise an exception if the value is close by in + # the call stack, for testing error handling. + if self.config._crash and self.config._crash in short_stack(limit=4): + raise Exception("Crashing because called by {}".format(self.config._crash)) + + def _write_startup_debug(self): + """Write out debug info at startup if needed.""" + wrote_any = False + with self._debug.without_callers(): + if self._debug.should('config'): + config_info = sorted(self.config.__dict__.items()) + config_info = [(k, v) for k, v in config_info if not k.startswith('_')] + write_formatted_info(self._debug, "config", config_info) + wrote_any = True + + if self._debug.should('sys'): + write_formatted_info(self._debug, "sys", self.sys_info()) + for plugin in self._plugins: + header = "sys: " + plugin._coverage_plugin_name + info = plugin.sys_info() + write_formatted_info(self._debug, header, info) + wrote_any = True + + if wrote_any: + write_formatted_info(self._debug, "end", ()) + + def _should_trace(self, filename, frame): + """Decide whether to trace execution in `filename`. + + Calls `_should_trace_internal`, and returns the FileDisposition. + + """ + disp = self._inorout.should_trace(filename, frame) + if self._debug.should('trace'): + self._debug.write(disposition_debug_msg(disp)) + return disp + + def _check_include_omit_etc(self, filename, frame): + """Check a file name against the include/omit/etc, rules, verbosely. + + Returns a boolean: True if the file should be traced, False if not. + + """ + reason = self._inorout.check_include_omit_etc(filename, frame) + if self._debug.should('trace'): + if not reason: + msg = "Including %r" % (filename,) + else: + msg = "Not including %r: %s" % (filename, reason) + self._debug.write(msg) + + return not reason + + def _warn(self, msg, slug=None, once=False): + """Use `msg` as a warning. + + For warning suppression, use `slug` as the shorthand. + + If `once` is true, only show this warning once (determined by the + slug.) + + """ + if self._no_warn_slugs is None: + self._no_warn_slugs = list(self.config.disable_warnings) + + if slug in self._no_warn_slugs: + # Don't issue the warning + return + + self._warnings.append(msg) + if slug: + msg = "%s (%s)" % (msg, slug) + if self._debug.should('pid'): + msg = "[%d] %s" % (os.getpid(), msg) + sys.stderr.write("Coverage.py warning: %s\n" % msg) + + if once: + self._no_warn_slugs.append(slug) + + def get_option(self, option_name): + """Get an option from the configuration. + + `option_name` is a colon-separated string indicating the section and + option name. For example, the ``branch`` option in the ``[run]`` + section of the config file would be indicated with `"run:branch"`. + + Returns the value of the option. The type depends on the option + selected. + + As a special case, an `option_name` of ``"paths"`` will return an + OrderedDict with the entire ``[paths]`` section value. + + .. versionadded:: 4.0 + + """ + return self.config.get_option(option_name) + + def set_option(self, option_name, value): + """Set an option in the configuration. + + `option_name` is a colon-separated string indicating the section and + option name. For example, the ``branch`` option in the ``[run]`` + section of the config file would be indicated with ``"run:branch"``. + + `value` is the new value for the option. This should be an + appropriate Python value. For example, use True for booleans, not the + string ``"True"``. + + As an example, calling:: + + cov.set_option("run:branch", True) + + has the same effect as this configuration file:: + + [run] + branch = True + + As a special case, an `option_name` of ``"paths"`` will replace the + entire ``[paths]`` section. The value should be an OrderedDict. + + .. versionadded:: 4.0 + + """ + self.config.set_option(option_name, value) + + def load(self): + """Load previously-collected coverage data from the data file.""" + self._init() + if self._collector: + self._collector.reset() + should_skip = self.config.parallel and not os.path.exists(self.config.data_file) + if not should_skip: + self._init_data(suffix=None) + self._post_init() + if not should_skip: + self._data.read() + + def _init_for_start(self): + """Initialization for start()""" + # Construct the collector. + concurrency = self.config.concurrency or () + if "multiprocessing" in concurrency: + if not patch_multiprocessing: + raise CoverageException( # pragma: only jython + "multiprocessing is not supported on this Python" + ) + patch_multiprocessing(rcfile=self.config.config_file) + + dycon = self.config.dynamic_context + if not dycon or dycon == "none": + context_switchers = [] + elif dycon == "test_function": + context_switchers = [should_start_context_test_function] + else: + raise CoverageException( + "Don't understand dynamic_context setting: {!r}".format(dycon) + ) + + context_switchers.extend( + plugin.dynamic_context for plugin in self._plugins.context_switchers + ) + + should_start_context = combine_context_switchers(context_switchers) + + self._collector = Collector( + should_trace=self._should_trace, + check_include=self._check_include_omit_etc, + should_start_context=should_start_context, + file_mapper=self._file_mapper, + timid=self.config.timid, + branch=self.config.branch, + warn=self._warn, + concurrency=concurrency, + ) + + suffix = self._data_suffix_specified + if suffix or self.config.parallel: + if not isinstance(suffix, string_class): + # if data_suffix=True, use .machinename.pid.random + suffix = True + else: + suffix = None + + self._init_data(suffix) + + self._collector.use_data(self._data, self.config.context) + + # Early warning if we aren't going to be able to support plugins. + if self._plugins.file_tracers and not self._collector.supports_plugins: + self._warn( + "Plugin file tracers (%s) aren't supported with %s" % ( + ", ".join( + plugin._coverage_plugin_name + for plugin in self._plugins.file_tracers + ), + self._collector.tracer_name(), + ) + ) + for plugin in self._plugins.file_tracers: + plugin._coverage_enabled = False + + # Create the file classifying substructure. + self._inorout = InOrOut( + warn=self._warn, + debug=(self._debug if self._debug.should('trace') else None), + ) + self._inorout.configure(self.config) + self._inorout.plugins = self._plugins + self._inorout.disp_class = self._collector.file_disposition_class + + # It's useful to write debug info after initing for start. + self._should_write_debug = True + + atexit.register(self._atexit) + + def _init_data(self, suffix): + """Create a data file if we don't have one yet.""" + if self._data is None: + # Create the data file. We do this at construction time so that the + # data file will be written into the directory where the process + # started rather than wherever the process eventually chdir'd to. + ensure_dir_for_file(self.config.data_file) + self._data = CoverageData( + basename=self.config.data_file, + suffix=suffix, + warn=self._warn, + debug=self._debug, + no_disk=self._no_disk, + ) + + def start(self): + """Start measuring code coverage. + + Coverage measurement only occurs in functions called after + :meth:`start` is invoked. Statements in the same scope as + :meth:`start` won't be measured. + + Once you invoke :meth:`start`, you must also call :meth:`stop` + eventually, or your process might not shut down cleanly. + + """ + self._init() + if not self._inited_for_start: + self._inited_for_start = True + self._init_for_start() + self._post_init() + + # Issue warnings for possible problems. + self._inorout.warn_conflicting_settings() + + # See if we think some code that would eventually be measured has + # already been imported. + if self._warn_preimported_source: + self._inorout.warn_already_imported_files() + + if self._auto_load: + self.load() + + self._collector.start() + self._started = True + self._instances.append(self) + + def stop(self): + """Stop measuring code coverage.""" + if self._instances: + if self._instances[-1] is self: + self._instances.pop() + if self._started: + self._collector.stop() + self._started = False + + def _atexit(self): + """Clean up on process shutdown.""" + if self._debug.should("process"): + self._debug.write("atexit: pid: {}, instance: {!r}".format(os.getpid(), self)) + if self._started: + self.stop() + if self._auto_save: + self.save() + + def erase(self): + """Erase previously collected coverage data. + + This removes the in-memory data collected in this session as well as + discarding the data file. + + """ + self._init() + self._post_init() + if self._collector: + self._collector.reset() + self._init_data(suffix=None) + self._data.erase(parallel=self.config.parallel) + self._data = None + self._inited_for_start = False + + def switch_context(self, new_context): + """Switch to a new dynamic context. + + `new_context` is a string to use as the :ref:`dynamic context + ` label for collected data. If a :ref:`static + context ` is in use, the static and dynamic context + labels will be joined together with a pipe character. + + Coverage collection must be started already. + + .. versionadded:: 5.0 + + """ + if not self._started: # pragma: part started + raise CoverageException( + "Cannot switch context, coverage is not started" + ) + + if self._collector.should_start_context: + self._warn("Conflicting dynamic contexts", slug="dynamic-conflict", once=True) + + self._collector.switch_context(new_context) + + def clear_exclude(self, which='exclude'): + """Clear the exclude list.""" + self._init() + setattr(self.config, which + "_list", []) + self._exclude_regex_stale() + + def exclude(self, regex, which='exclude'): + """Exclude source lines from execution consideration. + + A number of lists of regular expressions are maintained. Each list + selects lines that are treated differently during reporting. + + `which` determines which list is modified. The "exclude" list selects + lines that are not considered executable at all. The "partial" list + indicates lines with branches that are not taken. + + `regex` is a regular expression. The regex is added to the specified + list. If any of the regexes in the list is found in a line, the line + is marked for special treatment during reporting. + + """ + self._init() + excl_list = getattr(self.config, which + "_list") + excl_list.append(regex) + self._exclude_regex_stale() + + def _exclude_regex_stale(self): + """Drop all the compiled exclusion regexes, a list was modified.""" + self._exclude_re.clear() + + def _exclude_regex(self, which): + """Return a compiled regex for the given exclusion list.""" + if which not in self._exclude_re: + excl_list = getattr(self.config, which + "_list") + self._exclude_re[which] = join_regex(excl_list) + return self._exclude_re[which] + + def get_exclude_list(self, which='exclude'): + """Return a list of excluded regex patterns. + + `which` indicates which list is desired. See :meth:`exclude` for the + lists that are available, and their meaning. + + """ + self._init() + return getattr(self.config, which + "_list") + + def save(self): + """Save the collected coverage data to the data file.""" + data = self.get_data() + data.write() + + def combine(self, data_paths=None, strict=False): + """Combine together a number of similarly-named coverage data files. + + All coverage data files whose name starts with `data_file` (from the + coverage() constructor) will be read, and combined together into the + current measurements. + + `data_paths` is a list of files or directories from which data should + be combined. If no list is passed, then the data files from the + directory indicated by the current data file (probably the current + directory) will be combined. + + If `strict` is true, then it is an error to attempt to combine when + there are no data files to combine. + + .. versionadded:: 4.0 + The `data_paths` parameter. + + .. versionadded:: 4.3 + The `strict` parameter. + + """ + self._init() + self._init_data(suffix=None) + self._post_init() + self.get_data() + + aliases = None + if self.config.paths: + aliases = PathAliases() + for paths in self.config.paths.values(): + result = paths[0] + for pattern in paths[1:]: + aliases.add(pattern, result) + + combine_parallel_data(self._data, aliases=aliases, data_paths=data_paths, strict=strict) + + def get_data(self): + """Get the collected data. + + Also warn about various problems collecting data. + + Returns a :class:`coverage.CoverageData`, the collected coverage data. + + .. versionadded:: 4.0 + + """ + self._init() + self._init_data(suffix=None) + self._post_init() + + for plugin in self._plugins: + if not plugin._coverage_enabled: + self._collector.plugin_was_disabled(plugin) + + if self._collector and self._collector.flush_data(): + self._post_save_work() + + return self._data + + def _post_save_work(self): + """After saving data, look for warnings, post-work, etc. + + Warn about things that should have happened but didn't. + Look for unexecuted files. + + """ + # If there are still entries in the source_pkgs_unmatched list, + # then we never encountered those packages. + if self._warn_unimported_source: + self._inorout.warn_unimported_source() + + # Find out if we got any data. + if not self._data and self._warn_no_data: + self._warn("No data was collected.", slug="no-data-collected") + + # Touch all the files that could have executed, so that we can + # mark completely unexecuted files as 0% covered. + if self._data is not None: + file_paths = collections.defaultdict(list) + for file_path, plugin_name in self._inorout.find_possibly_unexecuted_files(): + file_path = self._file_mapper(file_path) + file_paths[plugin_name].append(file_path) + for plugin_name, paths in file_paths.items(): + self._data.touch_files(paths, plugin_name) + + if self.config.note: + self._warn("The '[run] note' setting is no longer supported.") + + # Backward compatibility with version 1. + def analysis(self, morf): + """Like `analysis2` but doesn't return excluded line numbers.""" + f, s, _, m, mf = self.analysis2(morf) + return f, s, m, mf + + def analysis2(self, morf): + """Analyze a module. + + `morf` is a module or a file name. It will be analyzed to determine + its coverage statistics. The return value is a 5-tuple: + + * The file name for the module. + * A list of line numbers of executable statements. + * A list of line numbers of excluded statements. + * A list of line numbers of statements not run (missing from + execution). + * A readable formatted string of the missing line numbers. + + The analysis uses the source file itself and the current measured + coverage data. + + """ + analysis = self._analyze(morf) + return ( + analysis.filename, + sorted(analysis.statements), + sorted(analysis.excluded), + sorted(analysis.missing), + analysis.missing_formatted(), + ) + + def _analyze(self, it): + """Analyze a single morf or code unit. + + Returns an `Analysis` object. + + """ + # All reporting comes through here, so do reporting initialization. + self._init() + Numbers.set_precision(self.config.precision) + self._post_init() + + data = self.get_data() + if not isinstance(it, FileReporter): + it = self._get_file_reporter(it) + + return Analysis(data, it, self._file_mapper) + + def _get_file_reporter(self, morf): + """Get a FileReporter for a module or file name.""" + plugin = None + file_reporter = "python" + + if isinstance(morf, string_class): + mapped_morf = self._file_mapper(morf) + plugin_name = self._data.file_tracer(mapped_morf) + if plugin_name: + plugin = self._plugins.get(plugin_name) + + if plugin: + file_reporter = plugin.file_reporter(mapped_morf) + if file_reporter is None: + raise CoverageException( + "Plugin %r did not provide a file reporter for %r." % ( + plugin._coverage_plugin_name, morf + ) + ) + + if file_reporter == "python": + file_reporter = PythonFileReporter(morf, self) + + return file_reporter + + def _get_file_reporters(self, morfs=None): + """Get a list of FileReporters for a list of modules or file names. + + For each module or file name in `morfs`, find a FileReporter. Return + the list of FileReporters. + + If `morfs` is a single module or file name, this returns a list of one + FileReporter. If `morfs` is empty or None, then the list of all files + measured is used to find the FileReporters. + + """ + if not morfs: + morfs = self._data.measured_files() + + # Be sure we have a collection. + if not isinstance(morfs, (list, tuple, set)): + morfs = [morfs] + + file_reporters = [self._get_file_reporter(morf) for morf in morfs] + return file_reporters + + def report( + self, morfs=None, show_missing=None, ignore_errors=None, + file=None, omit=None, include=None, skip_covered=None, + contexts=None, skip_empty=None, precision=None, sort=None + ): + """Write a textual summary report to `file`. + + Each module in `morfs` is listed, with counts of statements, executed + statements, missing statements, and a list of lines missed. + + If `show_missing` is true, then details of which lines or branches are + missing will be included in the report. If `ignore_errors` is true, + then a failure while reporting a single file will not stop the entire + report. + + `file` is a file-like object, suitable for writing. + + `include` is a list of file name patterns. Files that match will be + included in the report. Files matching `omit` will not be included in + the report. + + If `skip_covered` is true, don't report on files with 100% coverage. + + If `skip_empty` is true, don't report on empty files (those that have + no statements). + + `contexts` is a list of regular expressions. Only data from + :ref:`dynamic contexts ` that match one of those + expressions (using :func:`re.search `) will be + included in the report. + + `precision` is the number of digits to display after the decimal + point for percentages. + + All of the arguments default to the settings read from the + :ref:`configuration file `. + + Returns a float, the total percentage covered. + + .. versionadded:: 4.0 + The `skip_covered` parameter. + + .. versionadded:: 5.0 + The `contexts` and `skip_empty` parameters. + + .. versionadded:: 5.2 + The `precision` parameter. + + """ + with override_config( + self, + ignore_errors=ignore_errors, report_omit=omit, report_include=include, + show_missing=show_missing, skip_covered=skip_covered, + report_contexts=contexts, skip_empty=skip_empty, precision=precision, + sort=sort + ): + reporter = SummaryReporter(self) + return reporter.report(morfs, outfile=file) + + def annotate( + self, morfs=None, directory=None, ignore_errors=None, + omit=None, include=None, contexts=None, + ): + """Annotate a list of modules. + + Each module in `morfs` is annotated. The source is written to a new + file, named with a ",cover" suffix, with each line prefixed with a + marker to indicate the coverage of the line. Covered lines have ">", + excluded lines have "-", and missing lines have "!". + + See :meth:`report` for other arguments. + + """ + with override_config(self, + ignore_errors=ignore_errors, report_omit=omit, + report_include=include, report_contexts=contexts, + ): + reporter = AnnotateReporter(self) + reporter.report(morfs, directory=directory) + + def html_report( + self, morfs=None, directory=None, ignore_errors=None, + omit=None, include=None, extra_css=None, title=None, + skip_covered=None, show_contexts=None, contexts=None, + skip_empty=None, precision=None, + ): + """Generate an HTML report. + + The HTML is written to `directory`. The file "index.html" is the + overview starting point, with links to more detailed pages for + individual modules. + + `extra_css` is a path to a file of other CSS to apply on the page. + It will be copied into the HTML directory. + + `title` is a text string (not HTML) to use as the title of the HTML + report. + + See :meth:`report` for other arguments. + + Returns a float, the total percentage covered. + + .. note:: + The HTML report files are generated incrementally based on the + source files and coverage results. If you modify the report files, + the changes will not be considered. You should be careful about + changing the files in the report folder. + + """ + with override_config(self, + ignore_errors=ignore_errors, report_omit=omit, report_include=include, + html_dir=directory, extra_css=extra_css, html_title=title, + skip_covered=skip_covered, show_contexts=show_contexts, report_contexts=contexts, + skip_empty=skip_empty, precision=precision, + ): + reporter = HtmlReporter(self) + return reporter.report(morfs) + + def xml_report( + self, morfs=None, outfile=None, ignore_errors=None, + omit=None, include=None, contexts=None, skip_empty=None, + ): + """Generate an XML report of coverage results. + + The report is compatible with Cobertura reports. + + Each module in `morfs` is included in the report. `outfile` is the + path to write the file to, "-" will write to stdout. + + See :meth:`report` for other arguments. + + Returns a float, the total percentage covered. + + """ + with override_config(self, + ignore_errors=ignore_errors, report_omit=omit, report_include=include, + xml_output=outfile, report_contexts=contexts, skip_empty=skip_empty, + ): + return render_report(self.config.xml_output, XmlReporter(self), morfs) + + def json_report( + self, morfs=None, outfile=None, ignore_errors=None, + omit=None, include=None, contexts=None, pretty_print=None, + show_contexts=None + ): + """Generate a JSON report of coverage results. + + Each module in `morfs` is included in the report. `outfile` is the + path to write the file to, "-" will write to stdout. + + See :meth:`report` for other arguments. + + Returns a float, the total percentage covered. + + .. versionadded:: 5.0 + + """ + with override_config(self, + ignore_errors=ignore_errors, report_omit=omit, report_include=include, + json_output=outfile, report_contexts=contexts, json_pretty_print=pretty_print, + json_show_contexts=show_contexts + ): + return render_report(self.config.json_output, JsonReporter(self), morfs) + + def sys_info(self): + """Return a list of (key, value) pairs showing internal information.""" + + import coverage as covmod + + self._init() + self._post_init() + + def plugin_info(plugins): + """Make an entry for the sys_info from a list of plug-ins.""" + entries = [] + for plugin in plugins: + entry = plugin._coverage_plugin_name + if not plugin._coverage_enabled: + entry += " (disabled)" + entries.append(entry) + return entries + + info = [ + ('version', covmod.__version__), + ('coverage', covmod.__file__), + ('tracer', self._collector.tracer_name() if self._collector else "-none-"), + ('CTracer', 'available' if CTracer else "unavailable"), + ('plugins.file_tracers', plugin_info(self._plugins.file_tracers)), + ('plugins.configurers', plugin_info(self._plugins.configurers)), + ('plugins.context_switchers', plugin_info(self._plugins.context_switchers)), + ('configs_attempted', self.config.attempted_config_files), + ('configs_read', self.config.config_files_read), + ('config_file', self.config.config_file), + ('config_contents', + repr(self.config._config_contents) + if self.config._config_contents + else '-none-' + ), + ('data_file', self._data.data_filename() if self._data is not None else "-none-"), + ('python', sys.version.replace('\n', '')), + ('platform', platform.platform()), + ('implementation', platform.python_implementation()), + ('executable', sys.executable), + ('def_encoding', sys.getdefaultencoding()), + ('fs_encoding', sys.getfilesystemencoding()), + ('pid', os.getpid()), + ('cwd', os.getcwd()), + ('path', sys.path), + ('environment', sorted( + ("%s = %s" % (k, v)) + for k, v in iitems(os.environ) + if any(slug in k for slug in ("COV", "PY")) + )), + ('command_line', " ".join(getattr(sys, 'argv', ['-none-']))), + ] + + if self._inorout: + info.extend(self._inorout.sys_info()) + + info.extend(CoverageData.sys_info()) + + return info + + +# Mega debugging... +# $set_env.py: COVERAGE_DEBUG_CALLS - Lots and lots of output about calls to Coverage. +if int(os.environ.get("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugging + from coverage.debug import decorate_methods, show_calls + + Coverage = decorate_methods(show_calls(show_args=True), butnot=['get_data'])(Coverage) + + +def process_startup(): + """Call this at Python start-up to perhaps measure coverage. + + If the environment variable COVERAGE_PROCESS_START is defined, coverage + measurement is started. The value of the variable is the config file + to use. + + There are two ways to configure your Python installation to invoke this + function when Python starts: + + #. Create or append to sitecustomize.py to add these lines:: + + import coverage + coverage.process_startup() + + #. Create a .pth file in your Python installation containing:: + + import coverage; coverage.process_startup() + + Returns the :class:`Coverage` instance that was started, or None if it was + not started by this call. + + """ + cps = os.environ.get("COVERAGE_PROCESS_START") + if not cps: + # No request for coverage, nothing to do. + return None + + # This function can be called more than once in a process. This happens + # because some virtualenv configurations make the same directory visible + # twice in sys.path. This means that the .pth file will be found twice, + # and executed twice, executing this function twice. We set a global + # flag (an attribute on this function) to indicate that coverage.py has + # already been started, so we can avoid doing it twice. + # + # https://github.com/nedbat/coveragepy/issues/340 has more details. + + if hasattr(process_startup, "coverage"): + # We've annotated this function before, so we must have already + # started coverage.py in this process. Nothing to do. + return None + + cov = Coverage(config_file=cps) + process_startup.coverage = cov + cov._warn_no_data = False + cov._warn_unimported_source = False + cov._warn_preimported_source = False + cov._auto_save = True + cov.start() + + return cov + + +def _prevent_sub_process_measurement(): + """Stop any subprocess auto-measurement from writing data.""" + auto_created_coverage = getattr(process_startup, "coverage", None) + if auto_created_coverage is not None: + auto_created_coverage._auto_save = False diff --git a/robot/lib/python3.8/site-packages/coverage/data.py b/robot/lib/python3.8/site-packages/coverage/data.py new file mode 100644 index 0000000000000000000000000000000000000000..82bf1d41c17bafd62630fe87451632f73f94feeb --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/data.py @@ -0,0 +1,124 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Coverage data for coverage.py. + +This file had the 4.x JSON data support, which is now gone. This file still +has storage-agnostic helpers, and is kept to avoid changing too many imports. +CoverageData is now defined in sqldata.py, and imported here to keep the +imports working. + +""" + +import glob +import os.path + +from coverage.misc import CoverageException, file_be_gone +from coverage.sqldata import CoverageData + + +def line_counts(data, fullpath=False): + """Return a dict summarizing the line coverage data. + + Keys are based on the file names, and values are the number of executed + lines. If `fullpath` is true, then the keys are the full pathnames of + the files, otherwise they are the basenames of the files. + + Returns a dict mapping file names to counts of lines. + + """ + summ = {} + if fullpath: + filename_fn = lambda f: f + else: + filename_fn = os.path.basename + for filename in data.measured_files(): + summ[filename_fn(filename)] = len(data.lines(filename)) + return summ + + +def add_data_to_hash(data, filename, hasher): + """Contribute `filename`'s data to the `hasher`. + + `hasher` is a `coverage.misc.Hasher` instance to be updated with + the file's data. It should only get the results data, not the run + data. + + """ + if data.has_arcs(): + hasher.update(sorted(data.arcs(filename) or [])) + else: + hasher.update(sorted(data.lines(filename) or [])) + hasher.update(data.file_tracer(filename)) + + +def combine_parallel_data(data, aliases=None, data_paths=None, strict=False): + """Combine a number of data files together. + + Treat `data.filename` as a file prefix, and combine the data from all + of the data files starting with that prefix plus a dot. + + If `aliases` is provided, it's a `PathAliases` object that is used to + re-map paths to match the local machine's. + + If `data_paths` is provided, it is a list of directories or files to + combine. Directories are searched for files that start with + `data.filename` plus dot as a prefix, and those files are combined. + + If `data_paths` is not provided, then the directory portion of + `data.filename` is used as the directory to search for data files. + + Every data file found and combined is then deleted from disk. If a file + cannot be read, a warning will be issued, and the file will not be + deleted. + + If `strict` is true, and no files are found to combine, an error is + raised. + + """ + # Because of the os.path.abspath in the constructor, data_dir will + # never be an empty string. + data_dir, local = os.path.split(data.base_filename()) + localdot = local + '.*' + + data_paths = data_paths or [data_dir] + files_to_combine = [] + for p in data_paths: + if os.path.isfile(p): + files_to_combine.append(os.path.abspath(p)) + elif os.path.isdir(p): + pattern = os.path.join(os.path.abspath(p), localdot) + files_to_combine.extend(glob.glob(pattern)) + else: + raise CoverageException("Couldn't combine from non-existent path '%s'" % (p,)) + + if strict and not files_to_combine: + raise CoverageException("No data to combine") + + files_combined = 0 + for f in files_to_combine: + if f == data.data_filename(): + # Sometimes we are combining into a file which is one of the + # parallel files. Skip that file. + if data._debug.should('dataio'): + data._debug.write("Skipping combining ourself: %r" % (f,)) + continue + if data._debug.should('dataio'): + data._debug.write("Combining data file %r" % (f,)) + try: + new_data = CoverageData(f, debug=data._debug) + new_data.read() + except CoverageException as exc: + if data._warn: + # The CoverageException has the file name in it, so just + # use the message as the warning. + data._warn(str(exc)) + else: + data.update(new_data, aliases=aliases) + files_combined += 1 + if data._debug.should('dataio'): + data._debug.write("Deleting combined data file %r" % (f,)) + file_be_gone(f) + + if strict and not files_combined: + raise CoverageException("No usable data files") diff --git a/robot/lib/python3.8/site-packages/coverage/debug.py b/robot/lib/python3.8/site-packages/coverage/debug.py new file mode 100644 index 0000000000000000000000000000000000000000..194f16f50dbcf05856ce16285bd30269904d953f --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/debug.py @@ -0,0 +1,406 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Control of and utilities for debugging.""" + +import contextlib +import functools +import inspect +import itertools +import os +import pprint +import sys +try: + import _thread +except ImportError: + import thread as _thread + +from coverage.backward import reprlib, StringIO +from coverage.misc import isolate_module + +os = isolate_module(os) + + +# When debugging, it can be helpful to force some options, especially when +# debugging the configuration mechanisms you usually use to control debugging! +# This is a list of forced debugging options. +FORCED_DEBUG = [] +FORCED_DEBUG_FILE = None + + +class DebugControl(object): + """Control and output for debugging.""" + + show_repr_attr = False # For SimpleReprMixin + + def __init__(self, options, output): + """Configure the options and output file for debugging.""" + self.options = list(options) + FORCED_DEBUG + self.suppress_callers = False + + filters = [] + if self.should('pid'): + filters.append(add_pid_and_tid) + self.output = DebugOutputFile.get_one( + output, + show_process=self.should('process'), + filters=filters, + ) + self.raw_output = self.output.outfile + + def __repr__(self): + return "" % (self.options, self.raw_output) + + def should(self, option): + """Decide whether to output debug information in category `option`.""" + if option == "callers" and self.suppress_callers: + return False + return (option in self.options) + + @contextlib.contextmanager + def without_callers(self): + """A context manager to prevent call stacks from being logged.""" + old = self.suppress_callers + self.suppress_callers = True + try: + yield + finally: + self.suppress_callers = old + + def write(self, msg): + """Write a line of debug output. + + `msg` is the line to write. A newline will be appended. + + """ + self.output.write(msg+"\n") + if self.should('self'): + caller_self = inspect.stack()[1][0].f_locals.get('self') + if caller_self is not None: + self.output.write("self: {!r}\n".format(caller_self)) + if self.should('callers'): + dump_stack_frames(out=self.output, skip=1) + self.output.flush() + + +class DebugControlString(DebugControl): + """A `DebugControl` that writes to a StringIO, for testing.""" + def __init__(self, options): + super(DebugControlString, self).__init__(options, StringIO()) + + def get_output(self): + """Get the output text from the `DebugControl`.""" + return self.raw_output.getvalue() + + +class NoDebugging(object): + """A replacement for DebugControl that will never try to do anything.""" + def should(self, option): # pylint: disable=unused-argument + """Should we write debug messages? Never.""" + return False + + +def info_header(label): + """Make a nice header string.""" + return "--{:-<60s}".format(" "+label+" ") + + +def info_formatter(info): + """Produce a sequence of formatted lines from info. + + `info` is a sequence of pairs (label, data). The produced lines are + nicely formatted, ready to print. + + """ + info = list(info) + if not info: + return + label_len = 30 + assert all(len(l) < label_len for l, _ in info) + for label, data in info: + if data == []: + data = "-none-" + if isinstance(data, (list, set, tuple)): + prefix = "%*s:" % (label_len, label) + for e in data: + yield "%*s %s" % (label_len+1, prefix, e) + prefix = "" + else: + yield "%*s: %s" % (label_len, label, data) + + +def write_formatted_info(writer, header, info): + """Write a sequence of (label,data) pairs nicely.""" + writer.write(info_header(header)) + for line in info_formatter(info): + writer.write(" %s" % line) + + +def short_stack(limit=None, skip=0): + """Return a string summarizing the call stack. + + The string is multi-line, with one line per stack frame. Each line shows + the function name, the file name, and the line number: + + ... + start_import_stop : /Users/ned/coverage/trunk/tests/coveragetest.py @95 + import_local_file : /Users/ned/coverage/trunk/tests/coveragetest.py @81 + import_local_file : /Users/ned/coverage/trunk/coverage/backward.py @159 + ... + + `limit` is the number of frames to include, defaulting to all of them. + + `skip` is the number of frames to skip, so that debugging functions can + call this and not be included in the result. + + """ + stack = inspect.stack()[limit:skip:-1] + return "\n".join("%30s : %s:%d" % (t[3], t[1], t[2]) for t in stack) + + +def dump_stack_frames(limit=None, out=None, skip=0): + """Print a summary of the stack to stdout, or someplace else.""" + out = out or sys.stdout + out.write(short_stack(limit=limit, skip=skip+1)) + out.write("\n") + + +def clipped_repr(text, numchars=50): + """`repr(text)`, but limited to `numchars`.""" + r = reprlib.Repr() + r.maxstring = numchars + return r.repr(text) + + +def short_id(id64): + """Given a 64-bit id, make a shorter 16-bit one.""" + id16 = 0 + for offset in range(0, 64, 16): + id16 ^= id64 >> offset + return id16 & 0xFFFF + + +def add_pid_and_tid(text): + """A filter to add pid and tid to debug messages.""" + # Thread ids are useful, but too long. Make a shorter one. + tid = "{:04x}".format(short_id(_thread.get_ident())) + text = "{:5d}.{}: {}".format(os.getpid(), tid, text) + return text + + +class SimpleReprMixin(object): + """A mixin implementing a simple __repr__.""" + simple_repr_ignore = ['simple_repr_ignore', '$coverage.object_id'] + + def __repr__(self): + show_attrs = ( + (k, v) for k, v in self.__dict__.items() + if getattr(v, "show_repr_attr", True) + and not callable(v) + and k not in self.simple_repr_ignore + ) + return "<{klass} @0x{id:x} {attrs}>".format( + klass=self.__class__.__name__, + id=id(self), + attrs=" ".join("{}={!r}".format(k, v) for k, v in show_attrs), + ) + + +def simplify(v): # pragma: debugging + """Turn things which are nearly dict/list/etc into dict/list/etc.""" + if isinstance(v, dict): + return {k:simplify(vv) for k, vv in v.items()} + elif isinstance(v, (list, tuple)): + return type(v)(simplify(vv) for vv in v) + elif hasattr(v, "__dict__"): + return simplify({'.'+k: v for k, v in v.__dict__.items()}) + else: + return v + + +def pp(v): # pragma: debugging + """Debug helper to pretty-print data, including SimpleNamespace objects.""" + # Might not be needed in 3.9+ + pprint.pprint(simplify(v)) + + +def filter_text(text, filters): + """Run `text` through a series of filters. + + `filters` is a list of functions. Each takes a string and returns a + string. Each is run in turn. + + Returns: the final string that results after all of the filters have + run. + + """ + clean_text = text.rstrip() + ending = text[len(clean_text):] + text = clean_text + for fn in filters: + lines = [] + for line in text.splitlines(): + lines.extend(fn(line).splitlines()) + text = "\n".join(lines) + return text + ending + + +class CwdTracker(object): # pragma: debugging + """A class to add cwd info to debug messages.""" + def __init__(self): + self.cwd = None + + def filter(self, text): + """Add a cwd message for each new cwd.""" + cwd = os.getcwd() + if cwd != self.cwd: + text = "cwd is now {!r}\n".format(cwd) + text + self.cwd = cwd + return text + + +class DebugOutputFile(object): # pragma: debugging + """A file-like object that includes pid and cwd information.""" + def __init__(self, outfile, show_process, filters): + self.outfile = outfile + self.show_process = show_process + self.filters = list(filters) + + if self.show_process: + self.filters.insert(0, CwdTracker().filter) + self.write("New process: executable: %r\n" % (sys.executable,)) + self.write("New process: cmd: %r\n" % (getattr(sys, 'argv', None),)) + if hasattr(os, 'getppid'): + self.write("New process: pid: %r, parent pid: %r\n" % (os.getpid(), os.getppid())) + + SYS_MOD_NAME = '$coverage.debug.DebugOutputFile.the_one' + + @classmethod + def get_one(cls, fileobj=None, show_process=True, filters=(), interim=False): + """Get a DebugOutputFile. + + If `fileobj` is provided, then a new DebugOutputFile is made with it. + + If `fileobj` isn't provided, then a file is chosen + (COVERAGE_DEBUG_FILE, or stderr), and a process-wide singleton + DebugOutputFile is made. + + `show_process` controls whether the debug file adds process-level + information, and filters is a list of other message filters to apply. + + `filters` are the text filters to apply to the stream to annotate with + pids, etc. + + If `interim` is true, then a future `get_one` can replace this one. + + """ + if fileobj is not None: + # Make DebugOutputFile around the fileobj passed. + return cls(fileobj, show_process, filters) + + # Because of the way igor.py deletes and re-imports modules, + # this class can be defined more than once. But we really want + # a process-wide singleton. So stash it in sys.modules instead of + # on a class attribute. Yes, this is aggressively gross. + the_one, is_interim = sys.modules.get(cls.SYS_MOD_NAME, (None, True)) + if the_one is None or is_interim: + if fileobj is None: + debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE) + if debug_file_name: + fileobj = open(debug_file_name, "a") + else: + fileobj = sys.stderr + the_one = cls(fileobj, show_process, filters) + sys.modules[cls.SYS_MOD_NAME] = (the_one, interim) + return the_one + + def write(self, text): + """Just like file.write, but filter through all our filters.""" + self.outfile.write(filter_text(text, self.filters)) + self.outfile.flush() + + def flush(self): + """Flush our file.""" + self.outfile.flush() + + +def log(msg, stack=False): # pragma: debugging + """Write a log message as forcefully as possible.""" + out = DebugOutputFile.get_one(interim=True) + out.write(msg+"\n") + if stack: + dump_stack_frames(out=out, skip=1) + + +def decorate_methods(decorator, butnot=(), private=False): # pragma: debugging + """A class decorator to apply a decorator to methods.""" + def _decorator(cls): + for name, meth in inspect.getmembers(cls, inspect.isroutine): + if name not in cls.__dict__: + continue + if name != "__init__": + if not private and name.startswith("_"): + continue + if name in butnot: + continue + setattr(cls, name, decorator(meth)) + return cls + return _decorator + + +def break_in_pudb(func): # pragma: debugging + """A function decorator to stop in the debugger for each call.""" + @functools.wraps(func) + def _wrapper(*args, **kwargs): + import pudb + sys.stdout = sys.__stdout__ + pudb.set_trace() + return func(*args, **kwargs) + return _wrapper + + +OBJ_IDS = itertools.count() +CALLS = itertools.count() +OBJ_ID_ATTR = "$coverage.object_id" + +def show_calls(show_args=True, show_stack=False, show_return=False): # pragma: debugging + """A method decorator to debug-log each call to the function.""" + def _decorator(func): + @functools.wraps(func) + def _wrapper(self, *args, **kwargs): + oid = getattr(self, OBJ_ID_ATTR, None) + if oid is None: + oid = "{:08d} {:04d}".format(os.getpid(), next(OBJ_IDS)) + setattr(self, OBJ_ID_ATTR, oid) + extra = "" + if show_args: + eargs = ", ".join(map(repr, args)) + ekwargs = ", ".join("{}={!r}".format(*item) for item in kwargs.items()) + extra += "(" + extra += eargs + if eargs and ekwargs: + extra += ", " + extra += ekwargs + extra += ")" + if show_stack: + extra += " @ " + extra += "; ".join(_clean_stack_line(l) for l in short_stack().splitlines()) + callid = next(CALLS) + msg = "{} {:04d} {}{}\n".format(oid, callid, func.__name__, extra) + DebugOutputFile.get_one(interim=True).write(msg) + ret = func(self, *args, **kwargs) + if show_return: + msg = "{} {:04d} {} return {!r}\n".format(oid, callid, func.__name__, ret) + DebugOutputFile.get_one(interim=True).write(msg) + return ret + return _wrapper + return _decorator + + +def _clean_stack_line(s): # pragma: debugging + """Simplify some paths in a stack trace, for compactness.""" + s = s.strip() + s = s.replace(os.path.dirname(__file__) + '/', '') + s = s.replace(os.path.dirname(os.__file__) + '/', '') + s = s.replace(sys.prefix + '/', '') + return s diff --git a/robot/lib/python3.8/site-packages/coverage/disposition.py b/robot/lib/python3.8/site-packages/coverage/disposition.py new file mode 100644 index 0000000000000000000000000000000000000000..9b9a997d8ae3a5162a30167388d45f18cf45b700 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/disposition.py @@ -0,0 +1,37 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Simple value objects for tracking what to do with files.""" + + +class FileDisposition(object): + """A simple value type for recording what to do with a file.""" + pass + + +# FileDisposition "methods": FileDisposition is a pure value object, so it can +# be implemented in either C or Python. Acting on them is done with these +# functions. + +def disposition_init(cls, original_filename): + """Construct and initialize a new FileDisposition object.""" + disp = cls() + disp.original_filename = original_filename + disp.canonical_filename = original_filename + disp.source_filename = None + disp.trace = False + disp.reason = "" + disp.file_tracer = None + disp.has_dynamic_filename = False + return disp + + +def disposition_debug_msg(disp): + """Make a nice debug message of what the FileDisposition is doing.""" + if disp.trace: + msg = "Tracing %r" % (disp.original_filename,) + if disp.file_tracer: + msg += ": will be traced by %r" % disp.file_tracer + else: + msg = "Not tracing %r: %s" % (disp.original_filename, disp.reason) + return msg diff --git a/robot/lib/python3.8/site-packages/coverage/env.py b/robot/lib/python3.8/site-packages/coverage/env.py new file mode 100644 index 0000000000000000000000000000000000000000..80153ecf11c13e19830f94b54fd40bb83a2b355a --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/env.py @@ -0,0 +1,99 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Determine facts about the environment.""" + +import os +import platform +import sys + +# Operating systems. +WINDOWS = sys.platform == "win32" +LINUX = sys.platform.startswith("linux") + +# Python versions. We amend version_info with one more value, a zero if an +# official version, or 1 if built from source beyond an official version. +PYVERSION = sys.version_info + (int(platform.python_version()[-1] == "+"),) +PY2 = PYVERSION < (3, 0) +PY3 = PYVERSION >= (3, 0) + +# Python implementations. +PYPY = (platform.python_implementation() == 'PyPy') +if PYPY: + PYPYVERSION = sys.pypy_version_info + +PYPY2 = PYPY and PY2 +PYPY3 = PYPY and PY3 + +JYTHON = (platform.python_implementation() == 'Jython') +IRONPYTHON = (platform.python_implementation() == 'IronPython') + +# Python behavior +class PYBEHAVIOR(object): + """Flags indicating this Python's behavior.""" + + # Is "if __debug__" optimized away? + optimize_if_debug = (not PYPY) + + # Is "if not __debug__" optimized away? + optimize_if_not_debug = (not PYPY) and (PYVERSION >= (3, 7, 0, 'alpha', 4)) + + # Is "if not __debug__" optimized away even better? + optimize_if_not_debug2 = (not PYPY) and (PYVERSION >= (3, 8, 0, 'beta', 1)) + + # Do we have yield-from? + yield_from = (PYVERSION >= (3, 3)) + + # Do we have PEP 420 namespace packages? + namespaces_pep420 = (PYVERSION >= (3, 3)) + + # Do .pyc files have the source file size recorded in them? + size_in_pyc = (PYVERSION >= (3, 3)) + + # Do we have async and await syntax? + async_syntax = (PYVERSION >= (3, 5)) + + # PEP 448 defined additional unpacking generalizations + unpackings_pep448 = (PYVERSION >= (3, 5)) + + # Can co_lnotab have negative deltas? + negative_lnotab = (PYVERSION >= (3, 6)) and not (PYPY and PYPYVERSION < (7, 2)) + + # Do .pyc files conform to PEP 552? Hash-based pyc's. + hashed_pyc_pep552 = (PYVERSION >= (3, 7, 0, 'alpha', 4)) + + # Python 3.7.0b3 changed the behavior of the sys.path[0] entry for -m. It + # used to be an empty string (meaning the current directory). It changed + # to be the actual path to the current directory, so that os.chdir wouldn't + # affect the outcome. + actual_syspath0_dash_m = (not PYPY) and (PYVERSION >= (3, 7, 0, 'beta', 3)) + + # When a break/continue/return statement in a try block jumps to a finally + # block, does the finally block do the break/continue/return (pre-3.8), or + # does the finally jump back to the break/continue/return (3.8) to do the + # work? + finally_jumps_back = (PYVERSION >= (3, 8)) + + # When a function is decorated, does the trace function get called for the + # @-line and also the def-line (new behavior in 3.8)? Or just the @-line + # (old behavior)? + trace_decorated_def = (PYVERSION >= (3, 8)) + + # Are while-true loops optimized into absolute jumps with no loop setup? + nix_while_true = (PYVERSION >= (3, 8)) + + # Python 3.9a1 made sys.argv[0] and other reported files absolute paths. + report_absolute_files = (PYVERSION >= (3, 9)) + +# Coverage.py specifics. + +# Are we using the C-implemented trace function? +C_TRACER = os.getenv('COVERAGE_TEST_TRACER', 'c') == 'c' + +# Are we coverage-measuring ourselves? +METACOV = os.getenv('COVERAGE_COVERAGE', '') != '' + +# Are we running our test suite? +# Even when running tests, you can use COVERAGE_TESTING=0 to disable the +# test-specific behavior like contracts. +TESTING = os.getenv('COVERAGE_TESTING', '') == 'True' diff --git a/robot/lib/python3.8/site-packages/coverage/execfile.py b/robot/lib/python3.8/site-packages/coverage/execfile.py new file mode 100644 index 0000000000000000000000000000000000000000..29409d517a246ef00f13217bbb41a819557a0974 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/execfile.py @@ -0,0 +1,362 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Execute files of Python code.""" + +import inspect +import marshal +import os +import struct +import sys +import types + +from coverage import env +from coverage.backward import BUILTINS +from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec +from coverage.files import canonical_filename, python_reported_file +from coverage.misc import CoverageException, ExceptionDuringRun, NoCode, NoSource, isolate_module +from coverage.phystokens import compile_unicode +from coverage.python import get_python_source + +os = isolate_module(os) + + +class DummyLoader(object): + """A shim for the pep302 __loader__, emulating pkgutil.ImpLoader. + + Currently only implements the .fullname attribute + """ + def __init__(self, fullname, *_args): + self.fullname = fullname + + +if importlib_util_find_spec: + def find_module(modulename): + """Find the module named `modulename`. + + Returns the file path of the module, the name of the enclosing + package, and the spec. + """ + try: + spec = importlib_util_find_spec(modulename) + except ImportError as err: + raise NoSource(str(err)) + if not spec: + raise NoSource("No module named %r" % (modulename,)) + pathname = spec.origin + packagename = spec.name + if spec.submodule_search_locations: + mod_main = modulename + ".__main__" + spec = importlib_util_find_spec(mod_main) + if not spec: + raise NoSource( + "No module named %s; " + "%r is a package and cannot be directly executed" + % (mod_main, modulename) + ) + pathname = spec.origin + packagename = spec.name + packagename = packagename.rpartition(".")[0] + return pathname, packagename, spec +else: + def find_module(modulename): + """Find the module named `modulename`. + + Returns the file path of the module, the name of the enclosing + package, and None (where a spec would have been). + """ + openfile = None + glo, loc = globals(), locals() + try: + # Search for the module - inside its parent package, if any - using + # standard import mechanics. + if '.' in modulename: + packagename, name = modulename.rsplit('.', 1) + package = __import__(packagename, glo, loc, ['__path__']) + searchpath = package.__path__ + else: + packagename, name = None, modulename + searchpath = None # "top-level search" in imp.find_module() + openfile, pathname, _ = imp.find_module(name, searchpath) + + # Complain if this is a magic non-file module. + if openfile is None and pathname is None: + raise NoSource( + "module does not live in a file: %r" % modulename + ) + + # If `modulename` is actually a package, not a mere module, then we + # pretend to be Python 2.7 and try running its __main__.py script. + if openfile is None: + packagename = modulename + name = '__main__' + package = __import__(packagename, glo, loc, ['__path__']) + searchpath = package.__path__ + openfile, pathname, _ = imp.find_module(name, searchpath) + except ImportError as err: + raise NoSource(str(err)) + finally: + if openfile: + openfile.close() + + return pathname, packagename, None + + +class PyRunner(object): + """Multi-stage execution of Python code. + + This is meant to emulate real Python execution as closely as possible. + + """ + def __init__(self, args, as_module=False): + self.args = args + self.as_module = as_module + + self.arg0 = args[0] + self.package = self.modulename = self.pathname = self.loader = self.spec = None + + def prepare(self): + """Set sys.path properly. + + This needs to happen before any importing, and without importing anything. + """ + if self.as_module: + if env.PYBEHAVIOR.actual_syspath0_dash_m: + path0 = os.getcwd() + else: + path0 = "" + elif os.path.isdir(self.arg0): + # Running a directory means running the __main__.py file in that + # directory. + path0 = self.arg0 + else: + path0 = os.path.abspath(os.path.dirname(self.arg0)) + + if os.path.isdir(sys.path[0]): + # sys.path fakery. If we are being run as a command, then sys.path[0] + # is the directory of the "coverage" script. If this is so, replace + # sys.path[0] with the directory of the file we're running, or the + # current directory when running modules. If it isn't so, then we + # don't know what's going on, and just leave it alone. + top_file = inspect.stack()[-1][0].f_code.co_filename + sys_path_0_abs = os.path.abspath(sys.path[0]) + top_file_dir_abs = os.path.abspath(os.path.dirname(top_file)) + sys_path_0_abs = canonical_filename(sys_path_0_abs) + top_file_dir_abs = canonical_filename(top_file_dir_abs) + if sys_path_0_abs != top_file_dir_abs: + path0 = None + + else: + # sys.path[0] is a file. Is the next entry the directory containing + # that file? + if sys.path[1] == os.path.dirname(sys.path[0]): + # Can it be right to always remove that? + del sys.path[1] + + if path0 is not None: + sys.path[0] = python_reported_file(path0) + + def _prepare2(self): + """Do more preparation to run Python code. + + Includes finding the module to run and adjusting sys.argv[0]. + This method is allowed to import code. + + """ + if self.as_module: + self.modulename = self.arg0 + pathname, self.package, self.spec = find_module(self.modulename) + if self.spec is not None: + self.modulename = self.spec.name + self.loader = DummyLoader(self.modulename) + self.pathname = os.path.abspath(pathname) + self.args[0] = self.arg0 = self.pathname + elif os.path.isdir(self.arg0): + # Running a directory means running the __main__.py file in that + # directory. + for ext in [".py", ".pyc", ".pyo"]: + try_filename = os.path.join(self.arg0, "__main__" + ext) + if os.path.exists(try_filename): + self.arg0 = try_filename + break + else: + raise NoSource("Can't find '__main__' module in '%s'" % self.arg0) + + if env.PY2: + self.arg0 = os.path.abspath(self.arg0) + + # Make a spec. I don't know if this is the right way to do it. + try: + import importlib.machinery + except ImportError: + pass + else: + try_filename = python_reported_file(try_filename) + self.spec = importlib.machinery.ModuleSpec("__main__", None, origin=try_filename) + self.spec.has_location = True + self.package = "" + self.loader = DummyLoader("__main__") + else: + if env.PY3: + self.loader = DummyLoader("__main__") + + self.arg0 = python_reported_file(self.arg0) + + def run(self): + """Run the Python code!""" + + self._prepare2() + + # Create a module to serve as __main__ + main_mod = types.ModuleType('__main__') + + from_pyc = self.arg0.endswith((".pyc", ".pyo")) + main_mod.__file__ = self.arg0 + if from_pyc: + main_mod.__file__ = main_mod.__file__[:-1] + if self.package is not None: + main_mod.__package__ = self.package + main_mod.__loader__ = self.loader + if self.spec is not None: + main_mod.__spec__ = self.spec + + main_mod.__builtins__ = BUILTINS + + sys.modules['__main__'] = main_mod + + # Set sys.argv properly. + sys.argv = self.args + + try: + # Make a code object somehow. + if from_pyc: + code = make_code_from_pyc(self.arg0) + else: + code = make_code_from_py(self.arg0) + except CoverageException: + raise + except Exception as exc: + msg = "Couldn't run '{filename}' as Python code: {exc.__class__.__name__}: {exc}" + raise CoverageException(msg.format(filename=self.arg0, exc=exc)) + + # Execute the code object. + # Return to the original directory in case the test code exits in + # a non-existent directory. + cwd = os.getcwd() + try: + exec(code, main_mod.__dict__) + except SystemExit: # pylint: disable=try-except-raise + # The user called sys.exit(). Just pass it along to the upper + # layers, where it will be handled. + raise + except Exception: + # Something went wrong while executing the user code. + # Get the exc_info, and pack them into an exception that we can + # throw up to the outer loop. We peel one layer off the traceback + # so that the coverage.py code doesn't appear in the final printed + # traceback. + typ, err, tb = sys.exc_info() + + # PyPy3 weirdness. If I don't access __context__, then somehow it + # is non-None when the exception is reported at the upper layer, + # and a nested exception is shown to the user. This getattr fixes + # it somehow? https://bitbucket.org/pypy/pypy/issue/1903 + getattr(err, '__context__', None) + + # Call the excepthook. + try: + if hasattr(err, "__traceback__"): + err.__traceback__ = err.__traceback__.tb_next + sys.excepthook(typ, err, tb.tb_next) + except SystemExit: # pylint: disable=try-except-raise + raise + except Exception: + # Getting the output right in the case of excepthook + # shenanigans is kind of involved. + sys.stderr.write("Error in sys.excepthook:\n") + typ2, err2, tb2 = sys.exc_info() + err2.__suppress_context__ = True + if hasattr(err2, "__traceback__"): + err2.__traceback__ = err2.__traceback__.tb_next + sys.__excepthook__(typ2, err2, tb2.tb_next) + sys.stderr.write("\nOriginal exception was:\n") + raise ExceptionDuringRun(typ, err, tb.tb_next) + else: + sys.exit(1) + finally: + os.chdir(cwd) + + +def run_python_module(args): + """Run a Python module, as though with ``python -m name args...``. + + `args` is the argument array to present as sys.argv, including the first + element naming the module being executed. + + This is a helper for tests, to encapsulate how to use PyRunner. + + """ + runner = PyRunner(args, as_module=True) + runner.prepare() + runner.run() + + +def run_python_file(args): + """Run a Python file as if it were the main program on the command line. + + `args` is the argument array to present as sys.argv, including the first + element naming the file being executed. `package` is the name of the + enclosing package, if any. + + This is a helper for tests, to encapsulate how to use PyRunner. + + """ + runner = PyRunner(args, as_module=False) + runner.prepare() + runner.run() + + +def make_code_from_py(filename): + """Get source from `filename` and make a code object of it.""" + # Open the source file. + try: + source = get_python_source(filename) + except (IOError, NoSource): + raise NoSource("No file to run: '%s'" % filename) + + code = compile_unicode(source, filename, "exec") + return code + + +def make_code_from_pyc(filename): + """Get a code object from a .pyc file.""" + try: + fpyc = open(filename, "rb") + except IOError: + raise NoCode("No file to run: '%s'" % filename) + + with fpyc: + # First four bytes are a version-specific magic number. It has to + # match or we won't run the file. + magic = fpyc.read(4) + if magic != PYC_MAGIC_NUMBER: + raise NoCode("Bad magic number in .pyc file: {} != {}".format(magic, PYC_MAGIC_NUMBER)) + + date_based = True + if env.PYBEHAVIOR.hashed_pyc_pep552: + flags = struct.unpack(' MAX_FLAT: + h = hashlib.sha1(name.encode('UTF-8')).hexdigest() + name = name[-(MAX_FLAT-len(h)-1):] + '_' + h + return name + + +if env.WINDOWS: + + _ACTUAL_PATH_CACHE = {} + _ACTUAL_PATH_LIST_CACHE = {} + + def actual_path(path): + """Get the actual path of `path`, including the correct case.""" + if env.PY2 and isinstance(path, unicode_class): + path = path.encode(sys.getfilesystemencoding()) + if path in _ACTUAL_PATH_CACHE: + return _ACTUAL_PATH_CACHE[path] + + head, tail = os.path.split(path) + if not tail: + # This means head is the drive spec: normalize it. + actpath = head.upper() + elif not head: + actpath = tail + else: + head = actual_path(head) + if head in _ACTUAL_PATH_LIST_CACHE: + files = _ACTUAL_PATH_LIST_CACHE[head] + else: + try: + files = os.listdir(head) + except Exception: + # This will raise OSError, or this bizarre TypeError: + # https://bugs.python.org/issue1776160 + files = [] + _ACTUAL_PATH_LIST_CACHE[head] = files + normtail = os.path.normcase(tail) + for f in files: + if os.path.normcase(f) == normtail: + tail = f + break + actpath = os.path.join(head, tail) + _ACTUAL_PATH_CACHE[path] = actpath + return actpath + +else: + def actual_path(filename): + """The actual path for non-Windows platforms.""" + return filename + + +if env.PY2: + @contract(returns='unicode') + def unicode_filename(filename): + """Return a Unicode version of `filename`.""" + if isinstance(filename, str): + encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() + filename = filename.decode(encoding, "replace") + return filename +else: + @contract(filename='unicode', returns='unicode') + def unicode_filename(filename): + """Return a Unicode version of `filename`.""" + return filename + + +@contract(returns='unicode') +def abs_file(path): + """Return the absolute normalized form of `path`.""" + try: + path = os.path.realpath(path) + except UnicodeError: + pass + path = os.path.abspath(path) + path = actual_path(path) + path = unicode_filename(path) + return path + + +def python_reported_file(filename): + """Return the string as Python would describe this file name.""" + if env.PYBEHAVIOR.report_absolute_files: + filename = os.path.abspath(filename) + return filename + + +RELATIVE_DIR = None +CANONICAL_FILENAME_CACHE = None +set_relative_directory() + + +def isabs_anywhere(filename): + """Is `filename` an absolute path on any OS?""" + return ntpath.isabs(filename) or posixpath.isabs(filename) + + +def prep_patterns(patterns): + """Prepare the file patterns for use in a `FnmatchMatcher`. + + If a pattern starts with a wildcard, it is used as a pattern + as-is. If it does not start with a wildcard, then it is made + absolute with the current directory. + + If `patterns` is None, an empty list is returned. + + """ + prepped = [] + for p in patterns or []: + if p.startswith(("*", "?")): + prepped.append(p) + else: + prepped.append(abs_file(p)) + return prepped + + +class TreeMatcher(object): + """A matcher for files in a tree. + + Construct with a list of paths, either files or directories. Paths match + with the `match` method if they are one of the files, or if they are + somewhere in a subtree rooted at one of the directories. + + """ + def __init__(self, paths): + self.paths = list(paths) + + def __repr__(self): + return "" % self.paths + + def info(self): + """A list of strings for displaying when dumping state.""" + return self.paths + + def match(self, fpath): + """Does `fpath` indicate a file in one of our trees?""" + for p in self.paths: + if fpath.startswith(p): + if fpath == p: + # This is the same file! + return True + if fpath[len(p)] == os.sep: + # This is a file in the directory + return True + return False + + +class ModuleMatcher(object): + """A matcher for modules in a tree.""" + def __init__(self, module_names): + self.modules = list(module_names) + + def __repr__(self): + return "" % (self.modules) + + def info(self): + """A list of strings for displaying when dumping state.""" + return self.modules + + def match(self, module_name): + """Does `module_name` indicate a module in one of our packages?""" + if not module_name: + return False + + for m in self.modules: + if module_name.startswith(m): + if module_name == m: + return True + if module_name[len(m)] == '.': + # This is a module in the package + return True + + return False + + +class FnmatchMatcher(object): + """A matcher for files by file name pattern.""" + def __init__(self, pats): + self.pats = list(pats) + self.re = fnmatches_to_regex(self.pats, case_insensitive=env.WINDOWS) + + def __repr__(self): + return "" % self.pats + + def info(self): + """A list of strings for displaying when dumping state.""" + return self.pats + + def match(self, fpath): + """Does `fpath` match one of our file name patterns?""" + return self.re.match(fpath) is not None + + +def sep(s): + """Find the path separator used in this string, or os.sep if none.""" + sep_match = re.search(r"[\\/]", s) + if sep_match: + the_sep = sep_match.group(0) + else: + the_sep = os.sep + return the_sep + + +def fnmatches_to_regex(patterns, case_insensitive=False, partial=False): + """Convert fnmatch patterns to a compiled regex that matches any of them. + + Slashes are always converted to match either slash or backslash, for + Windows support, even when running elsewhere. + + If `partial` is true, then the pattern will match if the target string + starts with the pattern. Otherwise, it must match the entire string. + + Returns: a compiled regex object. Use the .match method to compare target + strings. + + """ + regexes = (fnmatch.translate(pattern) for pattern in patterns) + # Python3.7 fnmatch translates "/" as "/". Before that, it translates as "\/", + # so we have to deal with maybe a backslash. + regexes = (re.sub(r"\\?/", r"[\\\\/]", regex) for regex in regexes) + + if partial: + # fnmatch always adds a \Z to match the whole string, which we don't + # want, so we remove the \Z. While removing it, we only replace \Z if + # followed by paren (introducing flags), or at end, to keep from + # destroying a literal \Z in the pattern. + regexes = (re.sub(r'\\Z(\(\?|$)', r'\1', regex) for regex in regexes) + + flags = 0 + if case_insensitive: + flags |= re.IGNORECASE + compiled = re.compile(join_regex(regexes), flags=flags) + + return compiled + + +class PathAliases(object): + """A collection of aliases for paths. + + When combining data files from remote machines, often the paths to source + code are different, for example, due to OS differences, or because of + serialized checkouts on continuous integration machines. + + A `PathAliases` object tracks a list of pattern/result pairs, and can + map a path through those aliases to produce a unified path. + + """ + def __init__(self): + self.aliases = [] + + def pprint(self): # pragma: debugging + """Dump the important parts of the PathAliases, for debugging.""" + for regex, result in self.aliases: + print("{!r} --> {!r}".format(regex.pattern, result)) + + def add(self, pattern, result): + """Add the `pattern`/`result` pair to the list of aliases. + + `pattern` is an `fnmatch`-style pattern. `result` is a simple + string. When mapping paths, if a path starts with a match against + `pattern`, then that match is replaced with `result`. This models + isomorphic source trees being rooted at different places on two + different machines. + + `pattern` can't end with a wildcard component, since that would + match an entire tree, and not just its root. + + """ + if len(pattern) > 1: + pattern = pattern.rstrip(r"\/") + + # The pattern can't end with a wildcard component. + if pattern.endswith("*"): + raise CoverageException("Pattern must not end with wildcards.") + pattern_sep = sep(pattern) + + # The pattern is meant to match a filepath. Let's make it absolute + # unless it already is, or is meant to match any prefix. + if not pattern.startswith('*') and not isabs_anywhere(pattern): + pattern = abs_file(pattern) + if not pattern.endswith(pattern_sep): + pattern += pattern_sep + + # Make a regex from the pattern. + regex = fnmatches_to_regex([pattern], case_insensitive=True, partial=True) + + # Normalize the result: it must end with a path separator. + result_sep = sep(result) + result = result.rstrip(r"\/") + result_sep + self.aliases.append((regex, result)) + + def map(self, path): + """Map `path` through the aliases. + + `path` is checked against all of the patterns. The first pattern to + match is used to replace the root of the path with the result root. + Only one pattern is ever used. If no patterns match, `path` is + returned unchanged. + + The separator style in the result is made to match that of the result + in the alias. + + Returns the mapped path. If a mapping has happened, this is a + canonical path. If no mapping has happened, it is the original value + of `path` unchanged. + + """ + for regex, result in self.aliases: + m = regex.match(path) + if m: + new = path.replace(m.group(0), result) + new = new.replace(sep(path), sep(result)) + new = canonical_filename(new) + return new + return path + + +def find_python_files(dirname): + """Yield all of the importable Python files in `dirname`, recursively. + + To be importable, the files have to be in a directory with a __init__.py, + except for `dirname` itself, which isn't required to have one. The + assumption is that `dirname` was specified directly, so the user knows + best, but sub-directories are checked for a __init__.py to be sure we only + find the importable files. + + """ + for i, (dirpath, dirnames, filenames) in enumerate(os.walk(dirname)): + if i > 0 and '__init__.py' not in filenames: + # If a directory doesn't have __init__.py, then it isn't + # importable and neither are its files + del dirnames[:] + continue + for filename in filenames: + # We're only interested in files that look like reasonable Python + # files: Must end with .py or .pyw, and must not have certain funny + # characters that probably mean they are editor junk. + if re.match(r"^[^.#~!$@%^&*()+=,]+\.pyw?$", filename): + yield os.path.join(dirpath, filename) diff --git a/robot/lib/python3.8/site-packages/coverage/fullcoverage/encodings.py b/robot/lib/python3.8/site-packages/coverage/fullcoverage/encodings.py new file mode 100644 index 0000000000000000000000000000000000000000..aeb416e4060e784d8eef285c7cd6550f48ef885f --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/fullcoverage/encodings.py @@ -0,0 +1,60 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""Imposter encodings module that installs a coverage-style tracer. + +This is NOT the encodings module; it is an imposter that sets up tracing +instrumentation and then replaces itself with the real encodings module. + +If the directory that holds this file is placed first in the PYTHONPATH when +using "coverage" to run Python's tests, then this file will become the very +first module imported by the internals of Python 3. It installs a +coverage.py-compatible trace function that can watch Standard Library modules +execute from the very earliest stages of Python's own boot process. This fixes +a problem with coverage.py - that it starts too late to trace the coverage of +many of the most fundamental modules in the Standard Library. + +""" + +import sys + +class FullCoverageTracer(object): + def __init__(self): + # `traces` is a list of trace events. Frames are tricky: the same + # frame object is used for a whole scope, with new line numbers + # written into it. So in one scope, all the frame objects are the + # same object, and will eventually all will point to the last line + # executed. So we keep the line numbers alongside the frames. + # The list looks like: + # + # traces = [ + # ((frame, event, arg), lineno), ... + # ] + # + self.traces = [] + + def fullcoverage_trace(self, *args): + frame, event, arg = args + self.traces.append((args, frame.f_lineno)) + return self.fullcoverage_trace + +sys.settrace(FullCoverageTracer().fullcoverage_trace) + +# In coverage/files.py is actual_filename(), which uses glob.glob. I don't +# understand why, but that use of glob borks everything if fullcoverage is in +# effect. So here we make an ugly hail-mary pass to switch off glob.glob over +# there. This means when using fullcoverage, Windows path names will not be +# their actual case. + +#sys.fullcoverage = True + +# Finally, remove our own directory from sys.path; remove ourselves from +# sys.modules; and re-import "encodings", which will be the real package +# this time. Note that the delete from sys.modules dictionary has to +# happen last, since all of the symbols in this module will become None +# at that exact moment, including "sys". + +parentdir = max(filter(__file__.startswith, sys.path), key=len) +sys.path.remove(parentdir) +del sys.modules['encodings'] +import encodings diff --git a/robot/lib/python3.8/site-packages/coverage/html.py b/robot/lib/python3.8/site-packages/coverage/html.py new file mode 100644 index 0000000000000000000000000000000000000000..247d2ae198b200554efa402ae28d886c0b106d84 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/html.py @@ -0,0 +1,512 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +"""HTML reporting for coverage.py.""" + +import datetime +import json +import os +import re +import shutil + +import coverage +from coverage import env +from coverage.backward import iitems, SimpleNamespace, format_local_datetime +from coverage.data import add_data_to_hash +from coverage.files import flat_rootname +from coverage.misc import CoverageException, ensure_dir, file_be_gone, Hasher, isolate_module +from coverage.report import get_analysis_to_report +from coverage.results import Numbers +from coverage.templite import Templite + +os = isolate_module(os) + + +# Static files are looked for in a list of places. +STATIC_PATH = [ + # The place Debian puts system Javascript libraries. + "/usr/share/javascript", + + # Our htmlfiles directory. + os.path.join(os.path.dirname(__file__), "htmlfiles"), +] + + +def data_filename(fname, pkgdir=""): + """Return the path to a data file of ours. + + The file is searched for on `STATIC_PATH`, and the first place it's found, + is returned. + + Each directory in `STATIC_PATH` is searched as-is, and also, if `pkgdir` + is provided, at that sub-directory. + + """ + tried = [] + for static_dir in STATIC_PATH: + static_filename = os.path.join(static_dir, fname) + if os.path.exists(static_filename): + return static_filename + else: + tried.append(static_filename) + if pkgdir: + static_filename = os.path.join(static_dir, pkgdir, fname) + if os.path.exists(static_filename): + return static_filename + else: + tried.append(static_filename) + raise CoverageException( + "Couldn't find static file %r from %r, tried: %r" % (fname, os.getcwd(), tried) + ) + + +def read_data(fname): + """Return the contents of a data file of ours.""" + with open(data_filename(fname)) as data_file: + return data_file.read() + + +def write_html(fname, html): + """Write `html` to `fname`, properly encoded.""" + html = re.sub(r"(\A\s+)|(\s+$)", "", html, flags=re.MULTILINE) + "\n" + with open(fname, "wb") as fout: + fout.write(html.encode('ascii', 'xmlcharrefreplace')) + + +class HtmlDataGeneration(object): + """Generate structured data to be turned into HTML reports.""" + + EMPTY = "(empty)" + + def __init__(self, cov): + self.coverage = cov + self.config = self.coverage.config + data = self.coverage.get_data() + self.has_arcs = data.has_arcs() + if self.config.show_contexts: + if data.measured_contexts() == set([""]): + self.coverage._warn("No contexts were measured") + data.set_query_contexts(self.config.report_contexts) + + def data_for_file(self, fr, analysis): + """Produce the data needed for one file's report.""" + if self.has_arcs: + missing_branch_arcs = analysis.missing_branch_arcs() + arcs_executed = analysis.arcs_executed() + + if self.config.show_contexts: + contexts_by_lineno = analysis.data.contexts_by_lineno(analysis.filename) + + lines = [] + + for lineno, tokens in enumerate(fr.source_token_lines(), start=1): + # Figure out how to mark this line. + category = None + short_annotations = [] + long_annotations = [] + + if lineno in analysis.excluded: + category = 'exc' + elif lineno in analysis.missing: + category = 'mis' + elif self.has_arcs and lineno in missing_branch_arcs: + category = 'par' + for b in missing_branch_arcs[lineno]: + if b < 0: + short_annotations.append("exit") + else: + short_annotations.append(b) + long_annotations.append(fr.missing_arc_description(lineno, b, arcs_executed)) + elif lineno in analysis.statements: + category = 'run' + + contexts = contexts_label = None + context_list = None + if category and self.config.show_contexts: + contexts = sorted(c or self.EMPTY for c in contexts_by_lineno[lineno]) + if contexts == [self.EMPTY]: + contexts_label = self.EMPTY + else: + contexts_label = "{} ctx".format(len(contexts)) + context_list = contexts + + lines.append(SimpleNamespace( + tokens=tokens, + number=lineno, + category=category, + statement=(lineno in analysis.statements), + contexts=contexts, + contexts_label=contexts_label, + context_list=context_list, + short_annotations=short_annotations, + long_annotations=long_annotations, + )) + + file_data = SimpleNamespace( + relative_filename=fr.relative_filename(), + nums=analysis.numbers, + lines=lines, + ) + + return file_data + + +class HtmlReporter(object): + """HTML reporting.""" + + # These files will be copied from the htmlfiles directory to the output + # directory. + STATIC_FILES = [ + ("style.css", ""), + ("jquery.min.js", "jquery"), + ("jquery.ba-throttle-debounce.min.js", "jquery-throttle-debounce"), + ("jquery.hotkeys.js", "jquery-hotkeys"), + ("jquery.isonscreen.js", "jquery-isonscreen"), + ("jquery.tablesorter.min.js", "jquery-tablesorter"), + ("coverage_html.js", ""), + ("keybd_closed.png", ""), + ("keybd_open.png", ""), + ("favicon_32.png", ""), + ] + + def __init__(self, cov): + self.coverage = cov + self.config = self.coverage.config + self.directory = self.config.html_dir + title = self.config.html_title + if env.PY2: + title = title.decode("utf8") + + if self.config.extra_css: + self.extra_css = os.path.basename(self.config.extra_css) + else: + self.extra_css = None + + self.data = self.coverage.get_data() + self.has_arcs = self.data.has_arcs() + + self.file_summaries = [] + self.all_files_nums = [] + self.incr = IncrementalChecker(self.directory) + self.datagen = HtmlDataGeneration(self.coverage) + self.totals = Numbers() + + self.template_globals = { + # Functions available in the templates. + 'escape': escape, + 'pair': pair, + 'len': len, + + # Constants for this report. + '__url__': coverage.__url__, + '__version__': coverage.__version__, + 'title': title, + 'time_stamp': format_local_datetime(datetime.datetime.now()), + 'extra_css': self.extra_css, + 'has_arcs': self.has_arcs, + 'show_contexts': self.config.show_contexts, + + # Constants for all reports. + # These css classes determine which lines are highlighted by default. + 'category': { + 'exc': 'exc show_exc', + 'mis': 'mis show_mis', + 'par': 'par run show_par', + 'run': 'run', + } + } + self.pyfile_html_source = read_data("pyfile.html") + self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals) + + def report(self, morfs): + """Generate an HTML report for `morfs`. + + `morfs` is a list of modules or file names. + + """ + # Read the status data and check that this run used the same + # global data as the last run. + self.incr.read() + self.incr.check_global_data(self.config, self.pyfile_html_source) + + # Process all the files. + for fr, analysis in get_analysis_to_report(self.coverage, morfs): + self.html_file(fr, analysis) + + if not self.all_files_nums: + raise CoverageException("No data to report.") + + self.totals = sum(self.all_files_nums) + + # Write the index file. + self.index_file() + + self.make_local_static_report_files() + return self.totals.n_statements and self.totals.pc_covered + + def make_local_static_report_files(self): + """Make local instances of static files for HTML report.""" + # The files we provide must always be copied. + for static, pkgdir in self.STATIC_FILES: + shutil.copyfile( + data_filename(static, pkgdir), + os.path.join(self.directory, static) + ) + + # The user may have extra CSS they want copied. + if self.extra_css: + shutil.copyfile( + self.config.extra_css, + os.path.join(self.directory, self.extra_css) + ) + + def html_file(self, fr, analysis): + """Generate an HTML file for one source file.""" + rootname = flat_rootname(fr.relative_filename()) + html_filename = rootname + ".html" + ensure_dir(self.directory) + html_path = os.path.join(self.directory, html_filename) + + # Get the numbers for this file. + nums = analysis.numbers + self.all_files_nums.append(nums) + + if self.config.skip_covered: + # Don't report on 100% files. + no_missing_lines = (nums.n_missing == 0) + no_missing_branches = (nums.n_partial_branches == 0) + if no_missing_lines and no_missing_branches: + # If there's an existing file, remove it. + file_be_gone(html_path) + return + + if self.config.skip_empty: + # Don't report on empty files. + if nums.n_statements == 0: + file_be_gone(html_path) + return + + # Find out if the file on disk is already correct. + if self.incr.can_skip_file(self.data, fr, rootname): + self.file_summaries.append(self.incr.index_info(rootname)) + return + + # Write the HTML page for this file. + file_data = self.datagen.data_for_file(fr, analysis) + for ldata in file_data.lines: + # Build the HTML for the line. + html = [] + for tok_type, tok_text in ldata.tokens: + if tok_type == "ws": + html.append(escape(tok_text)) + else: + tok_html = escape(tok_text) or ' ' + html.append( + u'{}'.format(tok_type, tok_html) + ) + ldata.html = ''.join(html) + + if ldata.short_annotations: + # 202F is NARROW NO-BREAK SPACE. + # 219B is RIGHTWARDS ARROW WITH STROKE. + ldata.annotate = u",   ".join( + u"{} ↛ {}".format(ldata.number, d) + for d in ldata.short_annotations + ) + else: + ldata.annotate = None + + if ldata.long_annotations: + longs = ldata.long_annotations + if len(longs) == 1: + ldata.annotate_long = longs[0] + else: + ldata.annotate_long = u"{:d} missed branches: {}".format( + len(longs), + u", ".join( + u"{:d}) {}".format(num, ann_long) + for num, ann_long in enumerate(longs, start=1) + ), + ) + else: + ldata.annotate_long = None + + css_classes = [] + if ldata.category: + css_classes.append(self.template_globals['category'][ldata.category]) + ldata.css_class = ' '.join(css_classes) or "pln" + + html = self.source_tmpl.render(file_data.__dict__) + write_html(html_path, html) + + # Save this file's information for the index file. + index_info = { + 'nums': nums, + 'html_filename': html_filename, + 'relative_filename': fr.relative_filename(), + } + self.file_summaries.append(index_info) + self.incr.set_index_info(rootname, index_info) + + def index_file(self): + """Write the index.html file for this report.""" + index_tmpl = Templite(read_data("index.html"), self.template_globals) + + html = index_tmpl.render({ + 'files': self.file_summaries, + 'totals': self.totals, + }) + + write_html(os.path.join(self.directory, "index.html"), html) + + # Write the latest hashes for next time. + self.incr.write() + + +class IncrementalChecker(object): + """Logic and data to support incremental reporting.""" + + STATUS_FILE = "status.json" + STATUS_FORMAT = 2 + + # pylint: disable=wrong-spelling-in-comment,useless-suppression + # The data looks like: + # + # { + # "format": 2, + # "globals": "540ee119c15d52a68a53fe6f0897346d", + # "version": "4.0a1", + # "files": { + # "cogapp___init__": { + # "hash": "e45581a5b48f879f301c0f30bf77a50c", + # "index": { + # "html_filename": "cogapp___init__.html", + # "relative_filename": "cogapp/__init__", + # "nums": [ 1, 14, 0, 0, 0, 0, 0 ] + # } + # }, + # ... + # "cogapp_whiteutils": { + # "hash": "8504bb427fc488c4176809ded0277d51", + # "index": { + # "html_filename": "cogapp_whiteutils.html", + # "relative_filename": "cogapp/whiteutils", + # "nums": [ 1, 59, 0, 1, 28, 2, 2 ] + # } + # } + # } + # } + + def __init__(self, directory): + self.directory = directory + self.reset() + + def reset(self): + """Initialize to empty. Causes all files to be reported.""" + self.globals = '' + self.files = {} + + def read(self): + """Read the information we stored last time.""" + usable = False + try: + status_file = os.path.join(self.directory, self.STATUS_FILE) + with open(status_file) as fstatus: + status = json.load(fstatus) + except (IOError, ValueError): + usable = False + else: + usable = True + if status['format'] != self.STATUS_FORMAT: + usable = False + elif status['version'] != coverage.__version__: + usable = False + + if usable: + self.files = {} + for filename, fileinfo in iitems(status['files']): + fileinfo['index']['nums'] = Numbers(*fileinfo['index']['nums']) + self.files[filename] = fileinfo + self.globals = status['globals'] + else: + self.reset() + + def write(self): + """Write the current status.""" + status_file = os.path.join(self.directory, self.STATUS_FILE) + files = {} + for filename, fileinfo in iitems(self.files): + fileinfo['index']['nums'] = fileinfo['index']['nums'].init_args() + files[filename] = fileinfo + + status = { + 'format': self.STATUS_FORMAT, + 'version': coverage.__version__, + 'globals': self.globals, + 'files': files, + } + with open(status_file, "w") as fout: + json.dump(status, fout, separators=(',', ':')) + + def check_global_data(self, *data): + """Check the global data that can affect incremental reporting.""" + m = Hasher() + for d in data: + m.update(d) + these_globals = m.hexdigest() + if self.globals != these_globals: + self.reset() + self.globals = these_globals + + def can_skip_file(self, data, fr, rootname): + """Can we skip reporting this file? + + `data` is a CoverageData object, `fr` is a `FileReporter`, and + `rootname` is the name being used for the file. + """ + m = Hasher() + m.update(fr.source().encode('utf-8')) + add_data_to_hash(data, fr.filename, m) + this_hash = m.hexdigest() + + that_hash = self.file_hash(rootname) + + if this_hash == that_hash: + # Nothing has changed to require the file to be reported again. + return True + else: + self.set_file_hash(rootname, this_hash) + return False + + def file_hash(self, fname): + """Get the hash of `fname`'s contents.""" + return self.files.get(fname, {}).get('hash', '') + + def set_file_hash(self, fname, val): + """Set the hash of `fname`'s contents.""" + self.files.setdefault(fname, {})['hash'] = val + + def index_info(self, fname): + """Get the information for index.html for `fname`.""" + return self.files.get(fname, {}).get('index', {}) + + def set_index_info(self, fname, info): + """Set the information for index.html for `fname`.""" + self.files.setdefault(fname, {})['index'] = info + + +# Helpers for templates and generating HTML + +def escape(t): + """HTML-escape the text in `t`. + + This is only suitable for HTML text, not attributes. + + """ + # Convert HTML special chars into HTML entities. + return t.replace("&", "&").replace("<", "<") + + +def pair(ratio): + """Format a pair of numbers so JavaScript can read them in an attribute.""" + return "%s %s" % ratio diff --git a/robot/lib/python3.8/site-packages/coverage/htmlfiles/coverage_html.js b/robot/lib/python3.8/site-packages/coverage/htmlfiles/coverage_html.js new file mode 100644 index 0000000000000000000000000000000000000000..6bc9fdf59893ed3054c6c2c5fd3a10bc1b8c5d89 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/htmlfiles/coverage_html.js @@ -0,0 +1,589 @@ +// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +// For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +// Coverage.py HTML report browser code. +/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ +/*global coverage: true, document, window, $ */ + +coverage = {}; + +// Find all the elements with shortkey_* class, and use them to assign a shortcut key. +coverage.assign_shortkeys = function () { + $("*[class*='shortkey_']").each(function (i, e) { + $.each($(e).attr("class").split(" "), function (i, c) { + if (/^shortkey_/.test(c)) { + $(document).bind('keydown', c.substr(9), function () { + $(e).click(); + }); + } + }); + }); +}; + +// Create the events for the help panel. +coverage.wire_up_help_panel = function () { + $("#keyboard_icon").click(function () { + // Show the help panel, and position it so the keyboard icon in the + // panel is in the same place as the keyboard icon in the header. + $(".help_panel").show(); + var koff = $("#keyboard_icon").offset(); + var poff = $("#panel_icon").position(); + $(".help_panel").offset({ + top: koff.top-poff.top, + left: koff.left-poff.left + }); + }); + $("#panel_icon").click(function () { + $(".help_panel").hide(); + }); +}; + +// Create the events for the filter box. +coverage.wire_up_filter = function () { + // Cache elements. + var table = $("table.index"); + var table_rows = table.find("tbody tr"); + var table_row_names = table_rows.find("td.name a"); + var no_rows = $("#no_rows"); + + // Create a duplicate table footer that we can modify with dynamic summed values. + var table_footer = $("table.index tfoot tr"); + var table_dynamic_footer = table_footer.clone(); + table_dynamic_footer.attr('class', 'total_dynamic hidden'); + table_footer.after(table_dynamic_footer); + + // Observe filter keyevents. + $("#filter").on("keyup change", $.debounce(150, function (event) { + var filter_value = $(this).val(); + + if (filter_value === "") { + // Filter box is empty, remove all filtering. + table_rows.removeClass("hidden"); + + // Show standard footer, hide dynamic footer. + table_footer.removeClass("hidden"); + table_dynamic_footer.addClass("hidden"); + + // Hide placeholder, show table. + if (no_rows.length > 0) { + no_rows.hide(); + } + table.show(); + + } + else { + // Filter table items by value. + var hidden = 0; + var shown = 0; + + // Hide / show elements. + $.each(table_row_names, function () { + var element = $(this).parents("tr"); + + if ($(this).text().indexOf(filter_value) === -1) { + // hide + element.addClass("hidden"); + hidden++; + } + else { + // show + element.removeClass("hidden"); + shown++; + } + }); + + // Show placeholder if no rows will be displayed. + if (no_rows.length > 0) { + if (shown === 0) { + // Show placeholder, hide table. + no_rows.show(); + table.hide(); + } + else { + // Hide placeholder, show table. + no_rows.hide(); + table.show(); + } + } + + // Manage dynamic header: + if (hidden > 0) { + // Calculate new dynamic sum values based on visible rows. + for (var column = 2; column < 20; column++) { + // Calculate summed value. + var cells = table_rows.find('td:nth-child(' + column + ')'); + if (!cells.length) { + // No more columns...! + break; + } + + var sum = 0, numer = 0, denom = 0; + $.each(cells.filter(':visible'), function () { + var ratio = $(this).data("ratio"); + if (ratio) { + var splitted = ratio.split(" "); + numer += parseInt(splitted[0], 10); + denom += parseInt(splitted[1], 10); + } + else { + sum += parseInt(this.innerHTML, 10); + } + }); + + // Get footer cell element. + var footer_cell = table_dynamic_footer.find('td:nth-child(' + column + ')'); + + // Set value into dynamic footer cell element. + if (cells[0].innerHTML.indexOf('%') > -1) { + // Percentage columns use the numerator and denominator, + // and adapt to the number of decimal places. + var match = /\.([0-9]+)/.exec(cells[0].innerHTML); + var places = 0; + if (match) { + places = match[1].length; + } + var pct = numer * 100 / denom; + footer_cell.text(pct.toFixed(places) + '%'); + } + else { + footer_cell.text(sum); + } + } + + // Hide standard footer, show dynamic footer. + table_footer.addClass("hidden"); + table_dynamic_footer.removeClass("hidden"); + } + else { + // Show standard footer, hide dynamic footer. + table_footer.removeClass("hidden"); + table_dynamic_footer.addClass("hidden"); + } + } + })); + + // Trigger change event on setup, to force filter on page refresh + // (filter value may still be present). + $("#filter").trigger("change"); +}; + +// Loaded on index.html +coverage.index_ready = function ($) { + // Look for a localStorage item containing previous sort settings: + var sort_list = []; + var storage_name = "COVERAGE_INDEX_SORT"; + var stored_list = undefined; + try { + stored_list = localStorage.getItem(storage_name); + } catch(err) {} + + if (stored_list) { + sort_list = JSON.parse('[[' + stored_list + ']]'); + } + + // Create a new widget which exists only to save and restore + // the sort order: + $.tablesorter.addWidget({ + id: "persistentSort", + + // Format is called by the widget before displaying: + format: function (table) { + if (table.config.sortList.length === 0 && sort_list.length > 0) { + // This table hasn't been sorted before - we'll use + // our stored settings: + $(table).trigger('sorton', [sort_list]); + } + else { + // This is not the first load - something has + // already defined sorting so we'll just update + // our stored value to match: + sort_list = table.config.sortList; + } + } + }); + + // Configure our tablesorter to handle the variable number of + // columns produced depending on report options: + var headers = []; + var col_count = $("table.index > thead > tr > th").length; + + headers[0] = { sorter: 'text' }; + for (i = 1; i < col_count-1; i++) { + headers[i] = { sorter: 'digit' }; + } + headers[col_count-1] = { sorter: 'percent' }; + + // Enable the table sorter: + $("table.index").tablesorter({ + widgets: ['persistentSort'], + headers: headers + }); + + coverage.assign_shortkeys(); + coverage.wire_up_help_panel(); + coverage.wire_up_filter(); + + // Watch for page unload events so we can save the final sort settings: + $(window).on("unload", function () { + try { + localStorage.setItem(storage_name, sort_list.toString()) + } catch(err) {} + }); +}; + +// -- pyfile stuff -- + +coverage.pyfile_ready = function ($) { + // If we're directed to a particular line number, highlight the line. + var frag = location.hash; + if (frag.length > 2 && frag[1] === 't') { + $(frag).addClass('highlight'); + coverage.set_sel(parseInt(frag.substr(2), 10)); + } + else { + coverage.set_sel(0); + } + + $(document) + .bind('keydown', 'j', coverage.to_next_chunk_nicely) + .bind('keydown', 'k', coverage.to_prev_chunk_nicely) + .bind('keydown', '0', coverage.to_top) + .bind('keydown', '1', coverage.to_first_chunk) + ; + + $(".button_toggle_run").click(function (evt) {coverage.toggle_lines(evt.target, "run");}); + $(".button_toggle_exc").click(function (evt) {coverage.toggle_lines(evt.target, "exc");}); + $(".button_toggle_mis").click(function (evt) {coverage.toggle_lines(evt.target, "mis");}); + $(".button_toggle_par").click(function (evt) {coverage.toggle_lines(evt.target, "par");}); + + coverage.assign_shortkeys(); + coverage.wire_up_help_panel(); + + coverage.init_scroll_markers(); + + // Rebuild scroll markers when the window height changes. + $(window).resize(coverage.build_scroll_markers); +}; + +coverage.toggle_lines = function (btn, cls) { + btn = $(btn); + var show = "show_"+cls; + if (btn.hasClass(show)) { + $("#source ." + cls).removeClass(show); + btn.removeClass(show); + } + else { + $("#source ." + cls).addClass(show); + btn.addClass(show); + } + coverage.build_scroll_markers(); +}; + +// Return the nth line div. +coverage.line_elt = function (n) { + return $("#t" + n); +}; + +// Return the nth line number div. +coverage.num_elt = function (n) { + return $("#n" + n); +}; + +// Set the selection. b and e are line numbers. +coverage.set_sel = function (b, e) { + // The first line selected. + coverage.sel_begin = b; + // The next line not selected. + coverage.sel_end = (e === undefined) ? b+1 : e; +}; + +coverage.to_top = function () { + coverage.set_sel(0, 1); + coverage.scroll_window(0); +}; + +coverage.to_first_chunk = function () { + coverage.set_sel(0, 1); + coverage.to_next_chunk(); +}; + +// Return a string indicating what kind of chunk this line belongs to, +// or null if not a chunk. +coverage.chunk_indicator = function (line_elt) { + var klass = line_elt.attr('class'); + if (klass) { + var m = klass.match(/\bshow_\w+\b/); + if (m) { + return m[0]; + } + } + return null; +}; + +coverage.to_next_chunk = function () { + var c = coverage; + + // Find the start of the next colored chunk. + var probe = c.sel_end; + var chunk_indicator, probe_line; + while (true) { + probe_line = c.line_elt(probe); + if (probe_line.length === 0) { + return; + } + chunk_indicator = c.chunk_indicator(probe_line); + if (chunk_indicator) { + break; + } + probe++; + } + + // There's a next chunk, `probe` points to it. + var begin = probe; + + // Find the end of this chunk. + var next_indicator = chunk_indicator; + while (next_indicator === chunk_indicator) { + probe++; + probe_line = c.line_elt(probe); + next_indicator = c.chunk_indicator(probe_line); + } + c.set_sel(begin, probe); + c.show_selection(); +}; + +coverage.to_prev_chunk = function () { + var c = coverage; + + // Find the end of the prev colored chunk. + var probe = c.sel_begin-1; + var probe_line = c.line_elt(probe); + if (probe_line.length === 0) { + return; + } + var chunk_indicator = c.chunk_indicator(probe_line); + while (probe > 0 && !chunk_indicator) { + probe--; + probe_line = c.line_elt(probe); + if (probe_line.length === 0) { + return; + } + chunk_indicator = c.chunk_indicator(probe_line); + } + + // There's a prev chunk, `probe` points to its last line. + var end = probe+1; + + // Find the beginning of this chunk. + var prev_indicator = chunk_indicator; + while (prev_indicator === chunk_indicator) { + probe--; + probe_line = c.line_elt(probe); + prev_indicator = c.chunk_indicator(probe_line); + } + c.set_sel(probe+1, end); + c.show_selection(); +}; + +// Return the line number of the line nearest pixel position pos +coverage.line_at_pos = function (pos) { + var l1 = coverage.line_elt(1), + l2 = coverage.line_elt(2), + result; + if (l1.length && l2.length) { + var l1_top = l1.offset().top, + line_height = l2.offset().top - l1_top, + nlines = (pos - l1_top) / line_height; + if (nlines < 1) { + result = 1; + } + else { + result = Math.ceil(nlines); + } + } + else { + result = 1; + } + return result; +}; + +// Returns 0, 1, or 2: how many of the two ends of the selection are on +// the screen right now? +coverage.selection_ends_on_screen = function () { + if (coverage.sel_begin === 0) { + return 0; + } + + var top = coverage.line_elt(coverage.sel_begin); + var next = coverage.line_elt(coverage.sel_end-1); + + return ( + (top.isOnScreen() ? 1 : 0) + + (next.isOnScreen() ? 1 : 0) + ); +}; + +coverage.to_next_chunk_nicely = function () { + coverage.finish_scrolling(); + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: select the top line on + // the screen. + var win = $(window); + coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop())); + } + coverage.to_next_chunk(); +}; + +coverage.to_prev_chunk_nicely = function () { + coverage.finish_scrolling(); + if (coverage.selection_ends_on_screen() === 0) { + var win = $(window); + coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop() + win.height())); + } + coverage.to_prev_chunk(); +}; + +// Select line number lineno, or if it is in a colored chunk, select the +// entire chunk +coverage.select_line_or_chunk = function (lineno) { + var c = coverage; + var probe_line = c.line_elt(lineno); + if (probe_line.length === 0) { + return; + } + var the_indicator = c.chunk_indicator(probe_line); + if (the_indicator) { + // The line is in a highlighted chunk. + // Search backward for the first line. + var probe = lineno; + var indicator = the_indicator; + while (probe > 0 && indicator === the_indicator) { + probe--; + probe_line = c.line_elt(probe); + if (probe_line.length === 0) { + break; + } + indicator = c.chunk_indicator(probe_line); + } + var begin = probe + 1; + + // Search forward for the last line. + probe = lineno; + indicator = the_indicator; + while (indicator === the_indicator) { + probe++; + probe_line = c.line_elt(probe); + indicator = c.chunk_indicator(probe_line); + } + + coverage.set_sel(begin, probe); + } + else { + coverage.set_sel(lineno); + } +}; + +coverage.show_selection = function () { + var c = coverage; + + // Highlight the lines in the chunk + $(".linenos .highlight").removeClass("highlight"); + for (var probe = c.sel_begin; probe > 0 && probe < c.sel_end; probe++) { + c.num_elt(probe).addClass("highlight"); + } + + c.scroll_to_selection(); +}; + +coverage.scroll_to_selection = function () { + // Scroll the page if the chunk isn't fully visible. + if (coverage.selection_ends_on_screen() < 2) { + // Need to move the page. The html,body trick makes it scroll in all + // browsers, got it from http://stackoverflow.com/questions/3042651 + var top = coverage.line_elt(coverage.sel_begin); + var top_pos = parseInt(top.offset().top, 10); + coverage.scroll_window(top_pos - 30); + } +}; + +coverage.scroll_window = function (to_pos) { + $("html,body").animate({scrollTop: to_pos}, 200); +}; + +coverage.finish_scrolling = function () { + $("html,body").stop(true, true); +}; + +coverage.init_scroll_markers = function () { + var c = coverage; + // Init some variables + c.lines_len = $('#source p').length; + c.body_h = $('body').height(); + c.header_h = $('div#header').height(); + + // Build html + c.build_scroll_markers(); +}; + +coverage.build_scroll_markers = function () { + var c = coverage, + min_line_height = 3, + max_line_height = 10, + visible_window_h = $(window).height(); + + c.lines_to_mark = $('#source').find('p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par'); + $('#scroll_marker').remove(); + // Don't build markers if the window has no scroll bar. + if (c.body_h <= visible_window_h) { + return; + } + + $("body").append("
 
"); + var scroll_marker = $('#scroll_marker'), + marker_scale = scroll_marker.height() / c.body_h, + line_height = scroll_marker.height() / c.lines_len; + + // Line height must be between the extremes. + if (line_height > min_line_height) { + if (line_height > max_line_height) { + line_height = max_line_height; + } + } + else { + line_height = min_line_height; + } + + var previous_line = -99, + last_mark, + last_top, + offsets = {}; + + // Calculate line offsets outside loop to prevent relayouts + c.lines_to_mark.each(function() { + offsets[this.id] = $(this).offset().top; + }); + c.lines_to_mark.each(function () { + var id_name = $(this).attr('id'), + line_top = Math.round(offsets[id_name] * marker_scale), + line_number = parseInt(id_name.substring(1, id_name.length)); + + if (line_number === previous_line + 1) { + // If this solid missed block just make previous mark higher. + last_mark.css({ + 'height': line_top + line_height - last_top + }); + } + else { + // Add colored line in scroll_marker block. + scroll_marker.append('
'); + last_mark = $('#m' + line_number); + last_mark.css({ + 'height': line_height, + 'top': line_top + }); + last_top = line_top; + } + + previous_line = line_number; + }); +}; diff --git a/robot/lib/python3.8/site-packages/coverage/htmlfiles/favicon_32.png b/robot/lib/python3.8/site-packages/coverage/htmlfiles/favicon_32.png new file mode 100644 index 0000000000000000000000000000000000000000..8649f0475d8d20793b2ec431fe25a186a414cf10 Binary files /dev/null and b/robot/lib/python3.8/site-packages/coverage/htmlfiles/favicon_32.png differ diff --git a/robot/lib/python3.8/site-packages/coverage/htmlfiles/index.html b/robot/lib/python3.8/site-packages/coverage/htmlfiles/index.html new file mode 100644 index 0000000000000000000000000000000000000000..983db06125e63c747ed64696f4cd137b9b0e078b --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/htmlfiles/index.html @@ -0,0 +1,119 @@ +{# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 #} +{# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #} + + + + + + {{ title|escape }} + + + {% if extra_css %} + + {% endif %} + + + + + + + + + + + +
+ Hide keyboard shortcuts +

Hot-keys on this page

+
+

+ n + s + m + x + {% if has_arcs %} + b + p + {% endif %} + c   change column sorting +

+
+
+ +
+ + + {# The title="" attr doesn"t work in Safari. #} + + + + + + {% if has_arcs %} + + + {% endif %} + + + + {# HTML syntax requires thead, tfoot, tbody #} + + + + + + + {% if has_arcs %} + + + {% endif %} + + + + + {% for file in files %} + + + + + + {% if has_arcs %} + + + {% endif %} + + + {% endfor %} + +
Modulestatementsmissingexcludedbranchespartialcoverage
Total{{totals.n_statements}}{{totals.n_missing}}{{totals.n_excluded}}{{totals.n_branches}}{{totals.n_partial_branches}}{{totals.pc_covered_str}}%
{{file.relative_filename}}{{file.nums.n_statements}}{{file.nums.n_missing}}{{file.nums.n_excluded}}{{file.nums.n_branches}}{{file.nums.n_partial_branches}}{{file.nums.pc_covered_str}}%
+ +

+ No items found using the specified filter. +

+
+ + + + + diff --git a/robot/lib/python3.8/site-packages/coverage/htmlfiles/jquery.ba-throttle-debounce.min.js b/robot/lib/python3.8/site-packages/coverage/htmlfiles/jquery.ba-throttle-debounce.min.js new file mode 100644 index 0000000000000000000000000000000000000000..648fe5d3c22ceadb769337e20fe699abec8e078d --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/htmlfiles/jquery.ba-throttle-debounce.min.js @@ -0,0 +1,9 @@ +/* + * jQuery throttle / debounce - v1.1 - 3/7/2010 + * http://benalman.com/projects/jquery-throttle-debounce-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!=="boolean"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this); diff --git a/robot/lib/python3.8/site-packages/coverage/htmlfiles/jquery.hotkeys.js b/robot/lib/python3.8/site-packages/coverage/htmlfiles/jquery.hotkeys.js new file mode 100644 index 0000000000000000000000000000000000000000..09b21e03c7f058af138ef3ae7767ee24477ef6d1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/htmlfiles/jquery.hotkeys.js @@ -0,0 +1,99 @@ +/* + * jQuery Hotkeys Plugin + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * + * Based upon the plugin by Tzury Bar Yochay: + * http://github.com/tzuryby/hotkeys + * + * Original idea by: + * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ +*/ + +(function(jQuery){ + + jQuery.hotkeys = { + version: "0.8", + + specialKeys: { + 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", + 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", + 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", + 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", + 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", + 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", + 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" + }, + + shiftNums: { + "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", + "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", + ".": ">", "/": "?", "\\": "|" + } + }; + + function keyHandler( handleObj ) { + // Only care when a possible input has been specified + if ( typeof handleObj.data !== "string" ) { + return; + } + + var origHandler = handleObj.handler, + keys = handleObj.data.toLowerCase().split(" "); + + handleObj.handler = function( event ) { + // Don't fire in text-accepting inputs that we didn't directly bind to + if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || + event.target.type === "text") ) { + return; + } + + // Keypress represents characters, not special keys + var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], + character = String.fromCharCode( event.which ).toLowerCase(), + key, modif = "", possible = {}; + + // check combinations (alt|ctrl|shift+anything) + if ( event.altKey && special !== "alt" ) { + modif += "alt+"; + } + + if ( event.ctrlKey && special !== "ctrl" ) { + modif += "ctrl+"; + } + + // TODO: Need to make sure this works consistently across platforms + if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { + modif += "meta+"; + } + + if ( event.shiftKey && special !== "shift" ) { + modif += "shift+"; + } + + if ( special ) { + possible[ modif + special ] = true; + + } else { + possible[ modif + character ] = true; + possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; + + // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" + if ( modif === "shift+" ) { + possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; + } + } + + for ( var i = 0, l = keys.length; i < l; i++ ) { + if ( possible[ keys[i] ] ) { + return origHandler.apply( this, arguments ); + } + } + }; + } + + jQuery.each([ "keydown", "keyup", "keypress" ], function() { + jQuery.event.special[ this ] = { add: keyHandler }; + }); + +})( jQuery ); diff --git a/robot/lib/python3.8/site-packages/coverage/htmlfiles/jquery.isonscreen.js b/robot/lib/python3.8/site-packages/coverage/htmlfiles/jquery.isonscreen.js new file mode 100644 index 0000000000000000000000000000000000000000..0182ebd21372e5f84fa0aeb6d7c10f9f724002da --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/htmlfiles/jquery.isonscreen.js @@ -0,0 +1,53 @@ +/* Copyright (c) 2010 + * @author Laurence Wheway + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * + * @version 1.2.0 + */ +(function($) { + jQuery.extend({ + isOnScreen: function(box, container) { + //ensure numbers come in as intgers (not strings) and remove 'px' is it's there + for(var i in box){box[i] = parseFloat(box[i])}; + for(var i in container){container[i] = parseFloat(container[i])}; + + if(!container){ + container = { + left: $(window).scrollLeft(), + top: $(window).scrollTop(), + width: $(window).width(), + height: $(window).height() + } + } + + if( box.left+box.width-container.left > 0 && + box.left < container.width+container.left && + box.top+box.height-container.top > 0 && + box.top < container.height+container.top + ) return true; + return false; + } + }) + + + jQuery.fn.isOnScreen = function (container) { + for(var i in container){container[i] = parseFloat(container[i])}; + + if(!container){ + container = { + left: $(window).scrollLeft(), + top: $(window).scrollTop(), + width: $(window).width(), + height: $(window).height() + } + } + + if( $(this).offset().left+$(this).width()-container.left > 0 && + $(this).offset().left < container.width+container.left && + $(this).offset().top+$(this).height()-container.top > 0 && + $(this).offset().top < container.height+container.top + ) return true; + return false; + } +})(jQuery); diff --git a/robot/lib/python3.8/site-packages/coverage/htmlfiles/jquery.min.js b/robot/lib/python3.8/site-packages/coverage/htmlfiles/jquery.min.js new file mode 100644 index 0000000000000000000000000000000000000000..d1608e37ffa979b8689bfb868ad8b061b191f6f6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/coverage/htmlfiles/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h; +if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("', + html=True + ) diff --git a/robot/lib/python3.8/site-packages/robot/libraries/OperatingSystem.py b/robot/lib/python3.8/site-packages/robot/libraries/OperatingSystem.py new file mode 100644 index 0000000000000000000000000000000000000000..7ba014e420747c84506fbace7f3696927c6c5ee2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/libraries/OperatingSystem.py @@ -0,0 +1,1473 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import fnmatch +import glob +import io +import os +import shutil +import sys +import tempfile +import time + +from robot.version import get_version +from robot.api import logger +from robot.utils import (abspath, ConnectionCache, console_decode, del_env_var, + get_env_var, get_env_vars, get_time, is_truthy, + is_unicode, normpath, parse_time, plural_or_not, + secs_to_timestamp, secs_to_timestr, seq2str, + set_env_var, timestr_to_secs, unic, CONSOLE_ENCODING, + IRONPYTHON, JYTHON, PY2, PY3, SYSTEM_ENCODING, WINDOWS) + +__version__ = get_version() +PROCESSES = ConnectionCache('No active processes.') + + +class OperatingSystem(object): + """A test library providing keywords for OS related tasks. + + ``OperatingSystem`` is Robot Framework's standard library that + enables various operating system related tasks to be performed in + the system where Robot Framework is running. It can, among other + things, execute commands (e.g. `Run`), create and remove files and + directories (e.g. `Create File`, `Remove Directory`), check + whether files or directories exists or contain something + (e.g. `File Should Exist`, `Directory Should Be Empty`) and + manipulate environment variables (e.g. `Set Environment Variable`). + + == Table of contents == + + %TOC% + + = Path separators = + + Because Robot Framework uses the backslash (``\\``) as an escape character + in the test data, using a literal backslash requires duplicating it like + in ``c:\\\\path\\\\file.txt``. That can be inconvenient especially with + longer Windows paths, and thus all keywords expecting paths as arguments + convert forward slashes to backslashes automatically on Windows. This also + means that paths like ``${CURDIR}/path/file.txt`` are operating system + independent. + + Notice that the automatic path separator conversion does not work if + the path is only a part of an argument like with `Run` and `Start Process` + keywords. In these cases the built-in variable ``${/}`` that contains + ``\\`` or ``/``, depending on the operating system, can be used instead. + + = Pattern matching = + + Some keywords allow their arguments to be specified as + [http://en.wikipedia.org/wiki/Glob_(programming)|glob patterns] where: + + | ``*`` | matches any string, even an empty string | + | ``?`` | matches any single character | + | ``[chars]`` | matches one character in the bracket | + | ``[!chars]`` | matches one character not in the bracket | + | ``[a-z]`` | matches one character from the range in the bracket | + | ``[!a-z]`` | matches one character not from the range in the bracket | + + Unless otherwise noted, matching is case-insensitive on + case-insensitive operating systems such as Windows. + + Starting from Robot Framework 2.9.1, globbing is not done if the given path + matches an existing file even if it would contain a glob pattern. + + = Tilde expansion = + + Paths beginning with ``~`` or ``~username`` are expanded to the current or + specified user's home directory, respectively. The resulting path is + operating system dependent, but typically e.g. ``~/robot`` is expanded to + ``C:\\Users\\\\robot`` on Windows and ``/home//robot`` on + Unixes. + + The ``~username`` form does not work on Jython. + + = Boolean arguments = + + Some keywords accept arguments that are handled as Boolean values true or + false. If such an argument is given as a string, it is considered false if + it is an empty string or equal to ``FALSE``, ``NONE``, ``NO``, ``OFF`` or + ``0``, case-insensitively. Other strings are considered true regardless + their value, and other argument types are tested using the same + [http://docs.python.org/library/stdtypes.html#truth|rules as in Python]. + + True examples: + | `Remove Directory` | ${path} | recursive=True | # Strings are generally true. | + | `Remove Directory` | ${path} | recursive=yes | # Same as the above. | + | `Remove Directory` | ${path} | recursive=${TRUE} | # Python ``True`` is true. | + | `Remove Directory` | ${path} | recursive=${42} | # Numbers other than 0 are true. | + + False examples: + | `Remove Directory` | ${path} | recursive=False | # String ``false`` is false. | + | `Remove Directory` | ${path} | recursive=no | # Also string ``no`` is false. | + | `Remove Directory` | ${path} | recursive=${EMPTY} | # Empty string is false. | + | `Remove Directory` | ${path} | recursive=${FALSE} | # Python ``False`` is false. | + + Considering string ``NONE`` false is new in Robot Framework 3.0.3 and + considering also ``OFF`` and ``0`` false is new in Robot Framework 3.1. + + = Example = + + | =Setting= | =Value= | + | Library | OperatingSystem | + + | =Variable= | =Value= | + | ${PATH} | ${CURDIR}/example.txt | + + | =Test Case= | =Action= | =Argument= | =Argument= | + | Example | Create File | ${PATH} | Some text | + | | File Should Exist | ${PATH} | | + | | Copy File | ${PATH} | ~/file.txt | + | | ${output} = | Run | ${TEMPDIR}${/}script.py arg | + """ + ROBOT_LIBRARY_SCOPE = 'GLOBAL' + ROBOT_LIBRARY_VERSION = __version__ + + def run(self, command): + """Runs the given command in the system and returns the output. + + The execution status of the command *is not checked* by this + keyword, and it must be done separately based on the returned + output. If the execution return code is needed, either `Run + And Return RC` or `Run And Return RC And Output` can be used. + + The standard error stream is automatically redirected to the standard + output stream by adding ``2>&1`` after the executed command. This + automatic redirection is done only when the executed command does not + contain additional output redirections. You can thus freely forward + the standard error somewhere else, for example, like + ``my_command 2>stderr.txt``. + + The returned output contains everything written into the standard + output or error streams by the command (unless either of them + is redirected explicitly). Many commands add an extra newline + (``\\n``) after the output to make it easier to read in the + console. To ease processing the returned output, this possible + trailing newline is stripped by this keyword. + + Examples: + | ${output} = | Run | ls -lhF /tmp | + | Log | ${output} | + | ${result} = | Run | ${CURDIR}${/}tester.py arg1 arg2 | + | Should Not Contain | ${result} | FAIL | + | ${stdout} = | Run | /opt/script.sh 2>/tmp/stderr.txt | + | Should Be Equal | ${stdout} | TEST PASSED | + | File Should Be Empty | /tmp/stderr.txt | + + *TIP:* `Run Process` keyword provided by the + [http://robotframework.org/robotframework/latest/libraries/Process.html| + Process library] supports better process configuration and is generally + recommended as a replacement for this keyword. + """ + return self._run(command)[1] + + def run_and_return_rc(self, command): + """Runs the given command in the system and returns the return code. + + The return code (RC) is returned as a positive integer in + range from 0 to 255 as returned by the executed command. On + some operating systems (notable Windows) original return codes + can be something else, but this keyword always maps them to + the 0-255 range. Since the RC is an integer, it must be + checked e.g. with the keyword `Should Be Equal As Integers` + instead of `Should Be Equal` (both are built-in keywords). + + Examples: + | ${rc} = | Run and Return RC | ${CURDIR}${/}script.py arg | + | Should Be Equal As Integers | ${rc} | 0 | + | ${rc} = | Run and Return RC | /path/to/example.rb arg1 arg2 | + | Should Be True | 0 < ${rc} < 42 | + + See `Run` and `Run And Return RC And Output` if you need to get the + output of the executed command. + + *TIP:* `Run Process` keyword provided by the + [http://robotframework.org/robotframework/latest/libraries/Process.html| + Process library] supports better process configuration and is generally + recommended as a replacement for this keyword. + """ + return self._run(command)[0] + + def run_and_return_rc_and_output(self, command): + """Runs the given command in the system and returns the RC and output. + + The return code (RC) is returned similarly as with `Run And Return RC` + and the output similarly as with `Run`. + + Examples: + | ${rc} | ${output} = | Run and Return RC and Output | ${CURDIR}${/}mytool | + | Should Be Equal As Integers | ${rc} | 0 | + | Should Not Contain | ${output} | FAIL | + | ${rc} | ${stdout} = | Run and Return RC and Output | /opt/script.sh 2>/tmp/stderr.txt | + | Should Be True | ${rc} > 42 | + | Should Be Equal | ${stdout} | TEST PASSED | + | File Should Be Empty | /tmp/stderr.txt | + + *TIP:* `Run Process` keyword provided by the + [http://robotframework.org/robotframework/latest/libraries/Process.html| + Process library] supports better process configuration and is generally + recommended as a replacement for this keyword. + """ + return self._run(command) + + def _run(self, command): + process = _Process(command) + self._info("Running command '%s'." % process) + stdout = process.read() + rc = process.close() + return rc, stdout + + def get_file(self, path, encoding='UTF-8', encoding_errors='strict'): + """Returns the contents of a specified file. + + This keyword reads the specified file and returns the contents. + Line breaks in content are converted to platform independent form. + See also `Get Binary File`. + + ``encoding`` defines the encoding of the file. The default value is + ``UTF-8``, which means that UTF-8 and ASCII encoded files are read + correctly. In addition to the encodings supported by the underlying + Python implementation, the following special encoding values can be + used: + + - ``SYSTEM``: Use the default system encoding. + - ``CONSOLE``: Use the console encoding. Outside Windows this is same + as the system encoding. + + ``encoding_errors`` argument controls what to do if decoding some bytes + fails. All values accepted by ``decode`` method in Python are valid, but + in practice the following values are most useful: + + - ``strict``: Fail if characters cannot be decoded (default). + - ``ignore``: Ignore characters that cannot be decoded. + - ``replace``: Replace characters that cannot be decoded with + a replacement character. + + Support for ``SYSTEM`` and ``CONSOLE`` encodings in Robot Framework 3.0. + """ + path = self._absnorm(path) + self._link("Getting file '%s'.", path) + encoding = self._map_encoding(encoding) + if IRONPYTHON: + # https://github.com/IronLanguages/main/issues/1233 + with open(path) as f: + content = f.read().decode(encoding, encoding_errors) + else: + with io.open(path, encoding=encoding, errors=encoding_errors, + newline='') as f: + content = f.read() + return content.replace('\r\n', '\n') + + def _map_encoding(self, encoding): + # Python 3 opens files in native system encoding by default. + if PY3 and encoding.upper() == 'SYSTEM': + return None + return {'SYSTEM': SYSTEM_ENCODING, + 'CONSOLE': CONSOLE_ENCODING}.get(encoding.upper(), encoding) + + def get_binary_file(self, path): + """Returns the contents of a specified file. + + This keyword reads the specified file and returns the contents as is. + See also `Get File`. + """ + path = self._absnorm(path) + self._link("Getting file '%s'.", path) + with open(path, 'rb') as f: + return bytes(f.read()) + + def grep_file(self, path, pattern, encoding='UTF-8', encoding_errors='strict'): + """Returns the lines of the specified file that match the ``pattern``. + + This keyword reads a file from the file system using the defined + ``path``, ``encoding`` and ``encoding_errors`` similarly as `Get File`. + A difference is that only the lines that match the given ``pattern`` are + returned. Lines are returned as a single string catenated back together + with newlines and the number of matched lines is automatically logged. + Possible trailing newline is never returned. + + A line matches if it contains the ``pattern`` anywhere in it and + it *does not need to match the pattern fully*. The pattern + matching syntax is explained in `introduction`, and in this + case matching is case-sensitive. + + Examples: + | ${errors} = | Grep File | /var/log/myapp.log | ERROR | + | ${ret} = | Grep File | ${CURDIR}/file.txt | [Ww]ildc??d ex*ple | + + If more complex pattern matching is needed, it is possible to use + `Get File` in combination with String library keywords like `Get + Lines Matching Regexp`. + """ + pattern = '*%s*' % pattern + path = self._absnorm(path) + lines = [] + total_lines = 0 + self._link("Reading file '%s'.", path) + with io.open(path, encoding=encoding, errors=encoding_errors) as f: + for line in f.readlines(): + total_lines += 1 + line = line.rstrip('\r\n') + if fnmatch.fnmatchcase(line, pattern): + lines.append(line) + self._info('%d out of %d lines matched' % (len(lines), total_lines)) + return '\n'.join(lines) + + def log_file(self, path, encoding='UTF-8', encoding_errors='strict'): + """Wrapper for `Get File` that also logs the returned file. + + The file is logged with the INFO level. If you want something else, + just use `Get File` and the built-in keyword `Log` with the desired + level. + + See `Get File` for more information about ``encoding`` and + ``encoding_errors`` arguments. + """ + content = self.get_file(path, encoding, encoding_errors) + self._info(content) + return content + + # File and directory existence + + def should_exist(self, path, msg=None): + """Fails unless the given path (file or directory) exists. + + The path can be given as an exact path or as a glob pattern. + The pattern matching syntax is explained in `introduction`. + The default error message can be overridden with the ``msg`` argument. + """ + path = self._absnorm(path) + if not self._glob(path): + self._fail(msg, "Path '%s' does not exist." % path) + self._link("Path '%s' exists.", path) + + def should_not_exist(self, path, msg=None): + """Fails if the given path (file or directory) exists. + + The path can be given as an exact path or as a glob pattern. + The pattern matching syntax is explained in `introduction`. + The default error message can be overridden with the ``msg`` argument. + """ + path = self._absnorm(path) + matches = self._glob(path) + if matches: + self._fail(msg, self._get_matches_error('Path', path, matches)) + self._link("Path '%s' does not exist.", path) + + def _glob(self, path): + return glob.glob(path) if not os.path.exists(path) else [path] + + def _get_matches_error(self, what, path, matches): + if not self._is_glob_path(path): + return "%s '%s' exists." % (what, path) + return "%s '%s' matches %s." % (what, path, seq2str(sorted(matches))) + + def _is_glob_path(self, path): + return '*' in path or '?' in path or ('[' in path and ']' in path) + + def file_should_exist(self, path, msg=None): + """Fails unless the given ``path`` points to an existing file. + + The path can be given as an exact path or as a glob pattern. + The pattern matching syntax is explained in `introduction`. + The default error message can be overridden with the ``msg`` argument. + """ + path = self._absnorm(path) + matches = [p for p in self._glob(path) if os.path.isfile(p)] + if not matches: + self._fail(msg, "File '%s' does not exist." % path) + self._link("File '%s' exists.", path) + + def file_should_not_exist(self, path, msg=None): + """Fails if the given path points to an existing file. + + The path can be given as an exact path or as a glob pattern. + The pattern matching syntax is explained in `introduction`. + The default error message can be overridden with the ``msg`` argument. + """ + path = self._absnorm(path) + matches = [p for p in self._glob(path) if os.path.isfile(p)] + if matches: + self._fail(msg, self._get_matches_error('File', path, matches)) + self._link("File '%s' does not exist.", path) + + def directory_should_exist(self, path, msg=None): + """Fails unless the given path points to an existing directory. + + The path can be given as an exact path or as a glob pattern. + The pattern matching syntax is explained in `introduction`. + The default error message can be overridden with the ``msg`` argument. + """ + path = self._absnorm(path) + matches = [p for p in self._glob(path) if os.path.isdir(p)] + if not matches: + self._fail(msg, "Directory '%s' does not exist." % path) + self._link("Directory '%s' exists.", path) + + def directory_should_not_exist(self, path, msg=None): + """Fails if the given path points to an existing file. + + The path can be given as an exact path or as a glob pattern. + The pattern matching syntax is explained in `introduction`. + The default error message can be overridden with the ``msg`` argument. + """ + path = self._absnorm(path) + matches = [p for p in self._glob(path) if os.path.isdir(p)] + if matches: + self._fail(msg, self._get_matches_error('Directory', path, matches)) + self._link("Directory '%s' does not exist.", path) + + # Waiting file/dir to appear/disappear + + def wait_until_removed(self, path, timeout='1 minute'): + """Waits until the given file or directory is removed. + + The path can be given as an exact path or as a glob pattern. + The pattern matching syntax is explained in `introduction`. + If the path is a pattern, the keyword waits until all matching + items are removed. + + The optional ``timeout`` can be used to control the maximum time of + waiting. The timeout is given as a timeout string, e.g. in a format + ``15 seconds``, ``1min 10s`` or just ``10``. The time string format is + described in an appendix of Robot Framework User Guide. + + If the timeout is negative, the keyword is never timed-out. The keyword + returns immediately, if the path does not exist in the first place. + """ + path = self._absnorm(path) + timeout = timestr_to_secs(timeout) + maxtime = time.time() + timeout + while self._glob(path): + if timeout >= 0 and time.time() > maxtime: + self._fail("'%s' was not removed in %s." + % (path, secs_to_timestr(timeout))) + time.sleep(0.1) + self._link("'%s' was removed.", path) + + def wait_until_created(self, path, timeout='1 minute'): + """Waits until the given file or directory is created. + + The path can be given as an exact path or as a glob pattern. + The pattern matching syntax is explained in `introduction`. + If the path is a pattern, the keyword returns when an item matching + it is created. + + The optional ``timeout`` can be used to control the maximum time of + waiting. The timeout is given as a timeout string, e.g. in a format + ``15 seconds``, ``1min 10s`` or just ``10``. The time string format is + described in an appendix of Robot Framework User Guide. + + If the timeout is negative, the keyword is never timed-out. The keyword + returns immediately, if the path already exists. + """ + path = self._absnorm(path) + timeout = timestr_to_secs(timeout) + maxtime = time.time() + timeout + while not self._glob(path): + if timeout >= 0 and time.time() > maxtime: + self._fail("'%s' was not created in %s." + % (path, secs_to_timestr(timeout))) + time.sleep(0.1) + self._link("'%s' was created.", path) + + # Dir/file empty + + def directory_should_be_empty(self, path, msg=None): + """Fails unless the specified directory is empty. + + The default error message can be overridden with the ``msg`` argument. + """ + path = self._absnorm(path) + items = self._list_dir(path) + if items: + self._fail(msg, "Directory '%s' is not empty. Contents: %s." + % (path, seq2str(items, lastsep=', '))) + self._link("Directory '%s' is empty.", path) + + def directory_should_not_be_empty(self, path, msg=None): + """Fails if the specified directory is empty. + + The default error message can be overridden with the ``msg`` argument. + """ + path = self._absnorm(path) + items = self._list_dir(path) + if not items: + self._fail(msg, "Directory '%s' is empty." % path) + self._link("Directory '%%s' contains %d item%s." + % (len(items), plural_or_not(items)), path) + + def file_should_be_empty(self, path, msg=None): + """Fails unless the specified file is empty. + + The default error message can be overridden with the ``msg`` argument. + """ + path = self._absnorm(path) + if not os.path.isfile(path): + self._error("File '%s' does not exist." % path) + size = os.stat(path).st_size + if size > 0: + self._fail(msg, + "File '%s' is not empty. Size: %d bytes." % (path, size)) + self._link("File '%s' is empty.", path) + + def file_should_not_be_empty(self, path, msg=None): + """Fails if the specified directory is empty. + + The default error message can be overridden with the ``msg`` argument. + """ + path = self._absnorm(path) + if not os.path.isfile(path): + self._error("File '%s' does not exist." % path) + size = os.stat(path).st_size + if size == 0: + self._fail(msg, "File '%s' is empty." % path) + self._link("File '%%s' contains %d bytes." % size, path) + + # Creating and removing files and directory + + def create_file(self, path, content='', encoding='UTF-8'): + """Creates a file with the given content and encoding. + + If the directory where the file is created does not exist, it is + automatically created along with possible missing intermediate + directories. Possible existing file is overwritten. + + On Windows newline characters (``\\n``) in content are automatically + converted to Windows native newline sequence (``\\r\\n``). + + See `Get File` for more information about possible ``encoding`` values, + including special values ``SYSTEM`` and ``CONSOLE``. + + Examples: + | Create File | ${dir}/example.txt | Hello, world! | | + | Create File | ${path} | Hyv\\xe4 esimerkki | Latin-1 | + | Create File | /tmp/foo.txt | 3\\nlines\\nhere\\n | SYSTEM | + + Use `Append To File` if you want to append to an existing file + and `Create Binary File` if you need to write bytes without encoding. + `File Should Not Exist` can be used to avoid overwriting existing + files. + + The support for ``SYSTEM`` and ``CONSOLE`` encodings is new in Robot + Framework 3.0. Automatically converting ``\\n`` to ``\\r\\n`` on + Windows is new in Robot Framework 3.1. + """ + path = self._write_to_file(path, content, encoding) + self._link("Created file '%s'.", path) + + def _write_to_file(self, path, content, encoding=None, mode='w'): + path = self._absnorm(path) + parent = os.path.dirname(path) + if not os.path.exists(parent): + os.makedirs(parent) + # io.open() only accepts Unicode, not byte-strings, in text mode. + # We expect possible byte-strings to be all ASCII. + if PY2 and isinstance(content, str) and 'b' not in mode: + content = unicode(content) + if encoding: + encoding = self._map_encoding(encoding) + with io.open(path, mode, encoding=encoding) as f: + f.write(content) + return path + + def create_binary_file(self, path, content): + """Creates a binary file with the given content. + + If content is given as a Unicode string, it is first converted to bytes + character by character. All characters with ordinal below 256 can be + used and are converted to bytes with same values. Using characters + with higher ordinal is an error. + + Byte strings, and possible other types, are written to the file as is. + + If the directory for the file does not exist, it is created, along + with missing intermediate directories. + + Examples: + | Create Binary File | ${dir}/example.png | ${image content} | + | Create Binary File | ${path} | \\x01\\x00\\xe4\\x00 | + + Use `Create File` if you want to create a text file using a certain + encoding. `File Should Not Exist` can be used to avoid overwriting + existing files. + """ + if is_unicode(content): + content = bytes(bytearray(ord(c) for c in content)) + path = self._write_to_file(path, content, mode='wb') + self._link("Created binary file '%s'.", path) + + def append_to_file(self, path, content, encoding='UTF-8'): + """Appends the given content to the specified file. + + If the file exists, the given text is written to its end. If the file + does not exist, it is created. + + Other than not overwriting possible existing files, this keyword works + exactly like `Create File`. See its documentation for more details + about the usage. + + Note that special encodings ``SYSTEM`` and ``CONSOLE`` only work + with this keyword starting from Robot Framework 3.1.2. + """ + path = self._write_to_file(path, content, encoding, mode='a') + self._link("Appended to file '%s'.", path) + + def remove_file(self, path): + """Removes a file with the given path. + + Passes if the file does not exist, but fails if the path does + not point to a regular file (e.g. it points to a directory). + + The path can be given as an exact path or as a glob pattern. + The pattern matching syntax is explained in `introduction`. + If the path is a pattern, all files matching it are removed. + """ + path = self._absnorm(path) + matches = self._glob(path) + if not matches: + self._link("File '%s' does not exist.", path) + for match in matches: + if not os.path.isfile(match): + self._error("Path '%s' is not a file." % match) + os.remove(match) + self._link("Removed file '%s'.", match) + + def remove_files(self, *paths): + """Uses `Remove File` to remove multiple files one-by-one. + + Example: + | Remove Files | ${TEMPDIR}${/}foo.txt | ${TEMPDIR}${/}bar.txt | ${TEMPDIR}${/}zap.txt | + """ + for path in paths: + self.remove_file(path) + + def empty_directory(self, path): + """Deletes all the content from the given directory. + + Deletes both files and sub-directories, but the specified directory + itself if not removed. Use `Remove Directory` if you want to remove + the whole directory. + """ + path = self._absnorm(path) + for item in self._list_dir(path, absolute=True): + if os.path.isdir(item): + shutil.rmtree(item) + else: + os.remove(item) + self._link("Emptied directory '%s'.", path) + + def create_directory(self, path): + """Creates the specified directory. + + Also possible intermediate directories are created. Passes if the + directory already exists, but fails if the path exists and is not + a directory. + """ + path = self._absnorm(path) + if os.path.isdir(path): + self._link("Directory '%s' already exists.", path ) + elif os.path.exists(path): + self._error("Path '%s' is not a directory." % path) + else: + os.makedirs(path) + self._link("Created directory '%s'.", path) + + def remove_directory(self, path, recursive=False): + """Removes the directory pointed to by the given ``path``. + + If the second argument ``recursive`` is given a true value (see + `Boolean arguments`), the directory is removed recursively. Otherwise + removing fails if the directory is not empty. + + If the directory pointed to by the ``path`` does not exist, the keyword + passes, but it fails, if the ``path`` points to a file. + """ + path = self._absnorm(path) + if not os.path.exists(path): + self._link("Directory '%s' does not exist.", path) + elif not os.path.isdir(path): + self._error("Path '%s' is not a directory." % path) + else: + if is_truthy(recursive): + shutil.rmtree(path) + else: + self.directory_should_be_empty( + path, "Directory '%s' is not empty." % path) + os.rmdir(path) + self._link("Removed directory '%s'.", path) + + # Moving and copying files and directories + + def copy_file(self, source, destination): + """Copies the source file into the destination. + + Source must be a path to an existing file or a glob pattern (see + `Pattern matching`) that matches exactly one file. How the + destination is interpreted is explained below. + + 1) If the destination is an existing file, the source file is copied + over it. + + 2) If the destination is an existing directory, the source file is + copied into it. A possible file with the same name as the source is + overwritten. + + 3) If the destination does not exist and it ends with a path + separator (``/`` or ``\\``), it is considered a directory. That + directory is created and a source file copied into it. + Possible missing intermediate directories are also created. + + 4) If the destination does not exist and it does not end with a path + separator, it is considered a file. If the path to the file does not + exist, it is created. + + The resulting destination path is returned since Robot Framework 2.9.2. + + See also `Copy Files`, `Move File`, and `Move Files`. + """ + source, destination = \ + self._prepare_copy_and_move_file(source, destination) + if not self._are_source_and_destination_same_file(source, destination): + source, destination = self._atomic_copy(source, destination) + self._link("Copied file from '%s' to '%s'.", source, destination) + return destination + + def _prepare_copy_and_move_file(self, source, destination): + source = self._normalize_copy_and_move_source(source) + destination = self._normalize_copy_and_move_destination(destination) + if os.path.isdir(destination): + destination = os.path.join(destination, os.path.basename(source)) + return source, destination + + def _normalize_copy_and_move_source(self, source): + source = self._absnorm(source) + sources = self._glob(source) + if len(sources) > 1: + self._error("Multiple matches with source pattern '%s'." % source) + if sources: + source = sources[0] + if not os.path.exists(source): + self._error("Source file '%s' does not exist." % source) + if not os.path.isfile(source): + self._error("Source file '%s' is not a regular file." % source) + return source + + def _normalize_copy_and_move_destination(self, destination): + is_dir = os.path.isdir(destination) or destination.endswith(('/', '\\')) + destination = self._absnorm(destination) + directory = destination if is_dir else os.path.dirname(destination) + self._ensure_destination_directory_exists(directory) + return destination + + def _ensure_destination_directory_exists(self, path): + if not os.path.exists(path): + os.makedirs(path) + elif not os.path.isdir(path): + self._error("Destination '%s' exists and is not a directory." % path) + + def _are_source_and_destination_same_file(self, source, destination): + if self._force_normalize(source) == self._force_normalize(destination): + self._link("Source '%s' and destination '%s' point to the same " + "file.", source, destination) + return True + return False + + def _force_normalize(self, path): + # TODO: Should normalize_path also support link normalization? + # TODO: Should we handle dos paths like 'exampl~1.txt'? + return os.path.realpath(normpath(path, case_normalize=True)) + + def _atomic_copy(self, source, destination): + """Copy file atomically (or at least try to). + + This method tries to ensure that a file copy operation will not fail + if the destination file is removed during copy operation. The problem + is that copying a file is typically not an atomic operation. + + Luckily moving files is atomic in almost every platform, assuming files + are on the same filesystem, and we can use that as a workaround: + - First move the source to a temporary directory that is ensured to + be on the same filesystem as the destination. + - Move the temporary file over the real destination. + + See also https://github.com/robotframework/robotframework/issues/1502 + """ + temp_directory = tempfile.mkdtemp(dir=os.path.dirname(destination)) + temp_file = os.path.join(temp_directory, os.path.basename(source)) + try: + shutil.copy(source, temp_file) + if os.path.exists(destination): + os.remove(destination) + shutil.move(temp_file, destination) + finally: + shutil.rmtree(temp_directory) + return source, destination + + def move_file(self, source, destination): + """Moves the source file into the destination. + + Arguments have exactly same semantics as with `Copy File` keyword. + Destination file path is returned since Robot Framework 2.9.2. + + If the source and destination are on the same filesystem, rename + operation is used. Otherwise file is copied to the destination + filesystem and then removed from the original filesystem. + + See also `Move Files`, `Copy File`, and `Copy Files`. + """ + source, destination = \ + self._prepare_copy_and_move_file(source, destination) + if not self._are_source_and_destination_same_file(destination, source): + shutil.move(source, destination) + self._link("Moved file from '%s' to '%s'.", source, destination) + return destination + + def copy_files(self, *sources_and_destination): + """Copies specified files to the target directory. + + Source files can be given as exact paths and as glob patterns (see + `Pattern matching`). At least one source must be given, but it is + not an error if it is a pattern that does not match anything. + + Last argument must be the destination directory. If the destination + does not exist, it will be created. + + Examples: + | Copy Files | ${dir}/file1.txt | ${dir}/file2.txt | ${dir2} | + | Copy Files | ${dir}/file-*.txt | ${dir2} | | + + See also `Copy File`, `Move File`, and `Move Files`. + """ + sources, destination \ + = self._prepare_copy_and_move_files(sources_and_destination) + for source in sources: + self.copy_file(source, destination) + + def _prepare_copy_and_move_files(self, items): + if len(items) < 2: + self._error('Must contain destination and at least one source.') + sources = self._glob_files(items[:-1]) + destination = self._absnorm(items[-1]) + self._ensure_destination_directory_exists(destination) + return sources, destination + + def _glob_files(self, patterns): + files = [] + for pattern in patterns: + files.extend(self._glob(self._absnorm(pattern))) + return files + + def move_files(self, *sources_and_destination): + """Moves specified files to the target directory. + + Arguments have exactly same semantics as with `Copy Files` keyword. + + See also `Move File`, `Copy File`, and `Copy Files`. + """ + sources, destination \ + = self._prepare_copy_and_move_files(sources_and_destination) + for source in sources: + self.move_file(source, destination) + + def copy_directory(self, source, destination): + """Copies the source directory into the destination. + + If the destination exists, the source is copied under it. Otherwise + the destination directory and the possible missing intermediate + directories are created. + """ + source, destination \ + = self._prepare_copy_and_move_directory(source, destination) + try: + shutil.copytree(source, destination) + except shutil.Error: + # https://github.com/robotframework/robotframework/issues/2321 + if not (WINDOWS and JYTHON): + raise + self._link("Copied directory from '%s' to '%s'.", source, destination) + + def _prepare_copy_and_move_directory(self, source, destination): + source = self._absnorm(source) + destination = self._absnorm(destination) + if not os.path.exists(source): + self._error("Source '%s' does not exist." % source) + if not os.path.isdir(source): + self._error("Source '%s' is not a directory." % source) + if os.path.exists(destination) and not os.path.isdir(destination): + self._error("Destination '%s' is not a directory." % destination) + if os.path.exists(destination): + base = os.path.basename(source) + destination = os.path.join(destination, base) + else: + parent = os.path.dirname(destination) + if not os.path.exists(parent): + os.makedirs(parent) + return source, destination + + def move_directory(self, source, destination): + """Moves the source directory into a destination. + + Uses `Copy Directory` keyword internally, and ``source`` and + ``destination`` arguments have exactly same semantics as with + that keyword. + """ + source, destination \ + = self._prepare_copy_and_move_directory(source, destination) + shutil.move(source, destination) + self._link("Moved directory from '%s' to '%s'.", source, destination) + + # Environment Variables + + def get_environment_variable(self, name, default=None): + """Returns the value of an environment variable with the given name. + + If no such environment variable is set, returns the default value, if + given. Otherwise fails the test case. + + Returned variables are automatically decoded to Unicode using + the system encoding. + + Note that you can also access environment variables directly using + the variable syntax ``%{ENV_VAR_NAME}``. + """ + value = get_env_var(name, default) + if value is None: + self._error("Environment variable '%s' does not exist." % name) + return value + + def set_environment_variable(self, name, value): + """Sets an environment variable to a specified value. + + Values are converted to strings automatically. Set variables are + automatically encoded using the system encoding. + """ + set_env_var(name, value) + self._info("Environment variable '%s' set to value '%s'." + % (name, value)) + + def append_to_environment_variable(self, name, *values, **config): + """Appends given ``values`` to environment variable ``name``. + + If the environment variable already exists, values are added after it, + and otherwise a new environment variable is created. + + Values are, by default, joined together using the operating system + path separator (``;`` on Windows, ``:`` elsewhere). This can be changed + by giving a separator after the values like ``separator=value``. No + other configuration parameters are accepted. + + Examples (assuming ``NAME`` and ``NAME2`` do not exist initially): + | Append To Environment Variable | NAME | first | | + | Should Be Equal | %{NAME} | first | | + | Append To Environment Variable | NAME | second | third | + | Should Be Equal | %{NAME} | first${:}second${:}third | + | Append To Environment Variable | NAME2 | first | separator=- | + | Should Be Equal | %{NAME2} | first | | + | Append To Environment Variable | NAME2 | second | separator=- | + | Should Be Equal | %{NAME2} | first-second | + """ + sentinel = object() + initial = self.get_environment_variable(name, sentinel) + if initial is not sentinel: + values = (initial,) + values + separator = config.pop('separator', os.pathsep) + if config: + config = ['='.join(i) for i in sorted(config.items())] + self._error('Configuration %s not accepted.' + % seq2str(config, lastsep=' or ')) + self.set_environment_variable(name, separator.join(values)) + + def remove_environment_variable(self, *names): + """Deletes the specified environment variable. + + Does nothing if the environment variable is not set. + + It is possible to remove multiple variables by passing them to this + keyword as separate arguments. + """ + for name in names: + value = del_env_var(name) + if value: + self._info("Environment variable '%s' deleted." % name) + else: + self._info("Environment variable '%s' does not exist." % name) + + def environment_variable_should_be_set(self, name, msg=None): + """Fails if the specified environment variable is not set. + + The default error message can be overridden with the ``msg`` argument. + """ + value = get_env_var(name) + if not value: + self._fail(msg, "Environment variable '%s' is not set." % name) + self._info("Environment variable '%s' is set to '%s'." % (name, value)) + + def environment_variable_should_not_be_set(self, name, msg=None): + """Fails if the specified environment variable is set. + + The default error message can be overridden with the ``msg`` argument. + """ + value = get_env_var(name) + if value: + self._fail(msg, "Environment variable '%s' is set to '%s'." + % (name, value)) + self._info("Environment variable '%s' is not set." % name) + + def get_environment_variables(self): + """Returns currently available environment variables as a dictionary. + + Both keys and values are decoded to Unicode using the system encoding. + Altering the returned dictionary has no effect on the actual environment + variables. + """ + return get_env_vars() + + def log_environment_variables(self, level='INFO'): + """Logs all environment variables using the given log level. + + Environment variables are also returned the same way as with + `Get Environment Variables` keyword. + """ + variables = get_env_vars() + for name in sorted(variables, key=lambda item: item.lower()): + self._log('%s = %s' % (name, variables[name]), level) + return variables + + # Path + + def join_path(self, base, *parts): + """Joins the given path part(s) to the given base path. + + The path separator (``/`` or ``\\``) is inserted when needed and + the possible absolute paths handled as expected. The resulted + path is also normalized. + + Examples: + | ${path} = | Join Path | my | path | + | ${p2} = | Join Path | my/ | path/ | + | ${p3} = | Join Path | my | path | my | file.txt | + | ${p4} = | Join Path | my | /path | + | ${p5} = | Join Path | /my/path/ | .. | path2 | + => + - ${path} = 'my/path' + - ${p2} = 'my/path' + - ${p3} = 'my/path/my/file.txt' + - ${p4} = '/path' + - ${p5} = '/my/path2' + """ + base = base.replace('/', os.sep) + parts = [p.replace('/', os.sep) for p in parts] + return self.normalize_path(os.path.join(base, *parts)) + + def join_paths(self, base, *paths): + """Joins given paths with base and returns resulted paths. + + See `Join Path` for more information. + + Examples: + | @{p1} = | Join Paths | base | example | other | | + | @{p2} = | Join Paths | /my/base | /example | other | | + | @{p3} = | Join Paths | my/base | example/path/ | other | one/more | + => + - @{p1} = ['base/example', 'base/other'] + - @{p2} = ['/example', '/my/base/other'] + - @{p3} = ['my/base/example/path', 'my/base/other', 'my/base/one/more'] + """ + return [self.join_path(base, path) for path in paths] + + def normalize_path(self, path, case_normalize=False): + """Normalizes the given path. + + - Collapses redundant separators and up-level references. + - Converts ``/`` to ``\\`` on Windows. + - Replaces initial ``~`` or ``~user`` by that user's home directory. + The latter is not supported on Jython. + - If ``case_normalize`` is given a true value (see `Boolean arguments`) + on Windows, converts the path to all lowercase. New in Robot + Framework 3.1. + + Examples: + | ${path1} = | Normalize Path | abc/ | + | ${path2} = | Normalize Path | abc/../def | + | ${path3} = | Normalize Path | abc/./def//ghi | + | ${path4} = | Normalize Path | ~robot/stuff | + => + - ${path1} = 'abc' + - ${path2} = 'def' + - ${path3} = 'abc/def/ghi' + - ${path4} = '/home/robot/stuff' + + On Windows result would use ``\\`` instead of ``/`` and home directory + would be different. + """ + path = os.path.normpath(os.path.expanduser(path.replace('/', os.sep))) + # os.path.normcase doesn't normalize on OSX which also, by default, + # has case-insensitive file system. Our robot.utils.normpath would + # do that, but it's not certain would that, or other things that the + # utility do, desirable. + if case_normalize: + path = os.path.normcase(path) + return path or '.' + + def split_path(self, path): + """Splits the given path from the last path separator (``/`` or ``\\``). + + The given path is first normalized (e.g. a possible trailing + path separator is removed, special directories ``..`` and ``.`` + removed). The parts that are split are returned as separate + components. + + Examples: + | ${path1} | ${dir} = | Split Path | abc/def | + | ${path2} | ${file} = | Split Path | abc/def/ghi.txt | + | ${path3} | ${d2} = | Split Path | abc/../def/ghi/ | + => + - ${path1} = 'abc' & ${dir} = 'def' + - ${path2} = 'abc/def' & ${file} = 'ghi.txt' + - ${path3} = 'def' & ${d2} = 'ghi' + """ + return os.path.split(self.normalize_path(path)) + + def split_extension(self, path): + """Splits the extension from the given path. + + The given path is first normalized (e.g. possible trailing + path separators removed, special directories ``..`` and ``.`` + removed). The base path and extension are returned as separate + components so that the dot used as an extension separator is + removed. If the path contains no extension, an empty string is + returned for it. Possible leading and trailing dots in the file + name are never considered to be extension separators. + + Examples: + | ${path} | ${ext} = | Split Extension | file.extension | + | ${p2} | ${e2} = | Split Extension | path/file.ext | + | ${p3} | ${e3} = | Split Extension | path/file | + | ${p4} | ${e4} = | Split Extension | p1/../p2/file.ext | + | ${p5} | ${e5} = | Split Extension | path/.file.ext | + | ${p6} | ${e6} = | Split Extension | path/.file | + => + - ${path} = 'file' & ${ext} = 'extension' + - ${p2} = 'path/file' & ${e2} = 'ext' + - ${p3} = 'path/file' & ${e3} = '' + - ${p4} = 'p2/file' & ${e4} = 'ext' + - ${p5} = 'path/.file' & ${e5} = 'ext' + - ${p6} = 'path/.file' & ${e6} = '' + """ + path = self.normalize_path(path) + basename = os.path.basename(path) + if basename.startswith('.' * basename.count('.')): + return path, '' + if path.endswith('.'): + path2 = path.rstrip('.') + trailing_dots = '.' * (len(path) - len(path2)) + path = path2 + else: + trailing_dots = '' + basepath, extension = os.path.splitext(path) + if extension.startswith('.'): + extension = extension[1:] + if extension: + extension += trailing_dots + else: + basepath += trailing_dots + return basepath, extension + + # Misc + + def get_modified_time(self, path, format='timestamp'): + """Returns the last modification time of a file or directory. + + How time is returned is determined based on the given ``format`` + string as follows. Note that all checks are case-insensitive. + Returned time is also automatically logged. + + 1) If ``format`` contains the word ``epoch``, the time is returned + in seconds after the UNIX epoch. The return value is always + an integer. + + 2) If ``format`` contains any of the words ``year``, ``month``, + ``day``, ``hour``, ``min`` or ``sec``, only the selected parts are + returned. The order of the returned parts is always the one + in the previous sentence and the order of the words in + ``format`` is not significant. The parts are returned as + zero-padded strings (e.g. May -> ``05``). + + 3) Otherwise, and by default, the time is returned as a + timestamp string in the format ``2006-02-24 15:08:31``. + + Examples (when the modified time of ``${CURDIR}`` is + 2006-03-29 15:06:21): + | ${time} = | Get Modified Time | ${CURDIR} | + | ${secs} = | Get Modified Time | ${CURDIR} | epoch | + | ${year} = | Get Modified Time | ${CURDIR} | return year | + | ${y} | ${d} = | Get Modified Time | ${CURDIR} | year,day | + | @{time} = | Get Modified Time | ${CURDIR} | year,month,day,hour,min,sec | + => + - ${time} = '2006-03-29 15:06:21' + - ${secs} = 1143637581 + - ${year} = '2006' + - ${y} = '2006' & ${d} = '29' + - @{time} = ['2006', '03', '29', '15', '06', '21'] + """ + path = self._absnorm(path) + if not os.path.exists(path): + self._error("Path '%s' does not exist." % path) + mtime = get_time(format, os.stat(path).st_mtime) + self._link("Last modified time of '%%s' is %s." % mtime, path) + return mtime + + def set_modified_time(self, path, mtime): + """Sets the file modification and access times. + + Changes the modification and access times of the given file to + the value determined by ``mtime``. The time can be given in + different formats described below. Note that all checks + involving strings are case-insensitive. Modified time can only + be set to regular files. + + 1) If ``mtime`` is a number, or a string that can be converted + to a number, it is interpreted as seconds since the UNIX + epoch (1970-01-01 00:00:00 UTC). This documentation was + originally written about 1177654467 seconds after the epoch. + + 2) If ``mtime`` is a timestamp, that time will be used. Valid + timestamp formats are ``YYYY-MM-DD hh:mm:ss`` and + ``YYYYMMDD hhmmss``. + + 3) If ``mtime`` is equal to ``NOW``, the current local time is used. + + 4) If ``mtime`` is equal to ``UTC``, the current time in + [http://en.wikipedia.org/wiki/Coordinated_Universal_Time|UTC] + is used. + + 5) If ``mtime`` is in the format like ``NOW - 1 day`` or ``UTC + 1 + hour 30 min``, the current local/UTC time plus/minus the time + specified with the time string is used. The time string format + is described in an appendix of Robot Framework User Guide. + + Examples: + | Set Modified Time | /path/file | 1177654467 | # Time given as epoch seconds | + | Set Modified Time | /path/file | 2007-04-27 9:14:27 | # Time given as a timestamp | + | Set Modified Time | /path/file | NOW | # The local time of execution | + | Set Modified Time | /path/file | NOW - 1 day | # 1 day subtracted from the local time | + | Set Modified Time | /path/file | UTC + 1h 2min 3s | # 1h 2min 3s added to the UTC time | + """ + mtime = parse_time(mtime) + path = self._absnorm(path) + if not os.path.exists(path): + self._error("File '%s' does not exist." % path) + if not os.path.isfile(path): + self._error("Path '%s' is not a regular file." % path) + os.utime(path, (mtime, mtime)) + time.sleep(0.1) # Give os some time to really set these times + tstamp = secs_to_timestamp(mtime, seps=('-', ' ', ':')) + self._link("Set modified time of '%%s' to %s." % tstamp, path) + + def get_file_size(self, path): + """Returns and logs file size as an integer in bytes.""" + path = self._absnorm(path) + if not os.path.isfile(path): + self._error("File '%s' does not exist." % path) + size = os.stat(path).st_size + plural = plural_or_not(size) + self._link("Size of file '%%s' is %d byte%s." % (size, plural), path) + return size + + def list_directory(self, path, pattern=None, absolute=False): + """Returns and logs items in a directory, optionally filtered with ``pattern``. + + File and directory names are returned in case-sensitive alphabetical + order, e.g. ``['A Name', 'Second', 'a lower case name', 'one more']``. + Implicit directories ``.`` and ``..`` are not returned. The returned + items are automatically logged. + + File and directory names are returned relative to the given path + (e.g. ``'file.txt'``) by default. If you want them be returned in + absolute format (e.g. ``'/home/robot/file.txt'``), give the ``absolute`` + argument a true value (see `Boolean arguments`). + + If ``pattern`` is given, only items matching it are returned. The pattern + matching syntax is explained in `introduction`, and in this case + matching is case-sensitive. + + Examples (using also other `List Directory` variants): + | @{items} = | List Directory | ${TEMPDIR} | + | @{files} = | List Files In Directory | /tmp | *.txt | absolute | + | ${count} = | Count Files In Directory | ${CURDIR} | ??? | + """ + items = self._list_dir(path, pattern, absolute) + self._info('%d item%s:\n%s' % (len(items), plural_or_not(items), + '\n'.join(items))) + return items + + def list_files_in_directory(self, path, pattern=None, absolute=False): + """Wrapper for `List Directory` that returns only files.""" + files = self._list_files_in_dir(path, pattern, absolute) + self._info('%d file%s:\n%s' % (len(files), plural_or_not(files), + '\n'.join(files))) + return files + + def list_directories_in_directory(self, path, pattern=None, absolute=False): + """Wrapper for `List Directory` that returns only directories.""" + dirs = self._list_dirs_in_dir(path, pattern, absolute) + self._info('%d director%s:\n%s' % (len(dirs), + 'y' if len(dirs) == 1 else 'ies', + '\n'.join(dirs))) + return dirs + + def count_items_in_directory(self, path, pattern=None): + """Returns and logs the number of all items in the given directory. + + The argument ``pattern`` has the same semantics as with `List Directory` + keyword. The count is returned as an integer, so it must be checked e.g. + with the built-in keyword `Should Be Equal As Integers`. + """ + count = len(self._list_dir(path, pattern)) + self._info("%s item%s." % (count, plural_or_not(count))) + return count + + def count_files_in_directory(self, path, pattern=None): + """Wrapper for `Count Items In Directory` returning only file count.""" + count = len(self._list_files_in_dir(path, pattern)) + self._info("%s file%s." % (count, plural_or_not(count))) + return count + + def count_directories_in_directory(self, path, pattern=None): + """Wrapper for `Count Items In Directory` returning only directory count.""" + count = len(self._list_dirs_in_dir(path, pattern)) + self._info("%s director%s." % (count, 'y' if count == 1 else 'ies')) + return count + + def _list_dir(self, path, pattern=None, absolute=False): + path = self._absnorm(path) + self._link("Listing contents of directory '%s'.", path) + if not os.path.isdir(path): + self._error("Directory '%s' does not exist." % path) + # result is already unicode but unic also handles NFC normalization + items = sorted(unic(item) for item in os.listdir(path)) + if pattern: + items = [i for i in items if fnmatch.fnmatchcase(i, pattern)] + if is_truthy(absolute): + path = os.path.normpath(path) + items = [os.path.join(path, item) for item in items] + return items + + def _list_files_in_dir(self, path, pattern=None, absolute=False): + return [item for item in self._list_dir(path, pattern, absolute) + if os.path.isfile(os.path.join(path, item))] + + def _list_dirs_in_dir(self, path, pattern=None, absolute=False): + return [item for item in self._list_dir(path, pattern, absolute) + if os.path.isdir(os.path.join(path, item))] + + def touch(self, path): + """Emulates the UNIX touch command. + + Creates a file, if it does not exist. Otherwise changes its access and + modification times to the current time. + + Fails if used with the directories or the parent directory of the given + file does not exist. + """ + path = self._absnorm(path) + if os.path.isdir(path): + self._error("Cannot touch '%s' because it is a directory." % path) + if not os.path.exists(os.path.dirname(path)): + self._error("Cannot touch '%s' because its parent directory does " + "not exist." % path) + if os.path.exists(path): + mtime = round(time.time()) + os.utime(path, (mtime, mtime)) + self._link("Touched existing file '%s'.", path) + else: + open(path, 'w').close() + self._link("Touched new file '%s'.", path) + + def _absnorm(self, path): + path = self.normalize_path(path) + try: + return abspath(path) + except ValueError: # http://ironpython.codeplex.com/workitem/29489 + return path + + def _fail(self, *messages): + raise AssertionError(next(msg for msg in messages if msg)) + + def _error(self, msg): + raise RuntimeError(msg) + + def _info(self, msg): + self._log(msg, 'INFO') + + def _link(self, msg, *paths): + paths = tuple('%s' % (p, p) for p in paths) + self._log(msg % paths, 'HTML') + + def _warn(self, msg): + self._log(msg, 'WARN') + + def _log(self, msg, level): + logger.write(msg, level) + + +class _Process: + + def __init__(self, command): + self._command = self._process_command(command) + self._process = os.popen(self._command) + + def __str__(self): + return self._command + + def read(self): + return self._process_output(self._process.read()) + + def close(self): + try: + rc = self._process.close() + except IOError: # Has occurred sometimes in Windows + return 255 + if rc is None: + return 0 + # In Windows (Python and Jython) return code is value returned by + # command (can be almost anything) + # In other OS: + # In Jython return code can be between '-255' - '255' + # In Python return code must be converted with 'rc >> 8' and it is + # between 0-255 after conversion + if WINDOWS or JYTHON: + return rc % 256 + return rc >> 8 + + def _process_command(self, command): + if '>' not in command: + if command.endswith('&'): + command = command[:-1] + ' 2>&1 &' + else: + command += ' 2>&1' + return self._encode_to_file_system(command) + + def _encode_to_file_system(self, string): + enc = sys.getfilesystemencoding() if PY2 else None + return string.encode(enc) if enc else string + + def _process_output(self, output): + if '\r\n' in output: + output = output.replace('\r\n', '\n') + if output.endswith('\n'): + output = output[:-1] + return console_decode(output, force=True) diff --git a/robot/lib/python3.8/site-packages/robot/libraries/Process.py b/robot/lib/python3.8/site-packages/robot/libraries/Process.py new file mode 100644 index 0000000000000000000000000000000000000000..a38134ab1cfdafbb8a841b04690c84a320873077 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/libraries/Process.py @@ -0,0 +1,949 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import ctypes +import os +import subprocess +import time +import signal as signal_module + +from robot.utils import (ConnectionCache, abspath, cmdline2list, console_decode, + is_list_like, is_string, is_truthy, NormalizedDict, + py2to3, secs_to_timestr, system_decode, system_encode, + timestr_to_secs, IRONPYTHON, JYTHON, WINDOWS) +from robot.version import get_version +from robot.api import logger + + +class Process(object): + """Robot Framework test library for running processes. + + This library utilizes Python's + [http://docs.python.org/library/subprocess.html|subprocess] + module and its + [http://docs.python.org/library/subprocess.html#popen-constructor|Popen] + class. + + The library has following main usages: + + - Running processes in system and waiting for their completion using + `Run Process` keyword. + - Starting processes on background using `Start Process`. + - Waiting started process to complete using `Wait For Process` or + stopping them with `Terminate Process` or `Terminate All Processes`. + + == Table of contents == + + %TOC% + + = Specifying command and arguments = + + Both `Run Process` and `Start Process` accept the command to execute and + all arguments passed to the command as separate arguments. This makes usage + convenient and also allows these keywords to automatically escape possible + spaces and other special characters in commands and arguments. Notice that + if a command accepts options that themselves accept values, these options + and their values must be given as separate arguments. + + When `running processes in shell`, it is also possible to give the whole + command to execute as a single string. The command can then contain + multiple commands to be run together. When using this approach, the caller + is responsible on escaping. + + Examples: + | `Run Process` | ${tools}${/}prog.py | argument | second arg with spaces | + | `Run Process` | java | -jar | ${jars}${/}example.jar | --option | value | + | `Run Process` | prog.py "one arg" && tool.sh | shell=yes | cwd=${tools} | + + Possible non-string arguments are converted to strings automatically. + + = Process configuration = + + `Run Process` and `Start Process` keywords can be configured using + optional ``**configuration`` keyword arguments. Configuration arguments + must be given after other arguments passed to these keywords and must + use syntax like ``name=value``. Available configuration arguments are + listed below and discussed further in sections afterwards. + + | = Name = | = Explanation = | + | shell | Specifies whether to run the command in shell or not. | + | cwd | Specifies the working directory. | + | env | Specifies environment variables given to the process. | + | env: | Overrides the named environment variable(s) only. | + | stdout | Path of a file where to write standard output. | + | stderr | Path of a file where to write standard error. | + | output_encoding | Encoding to use when reading command outputs. | + | alias | Alias given to the process. | + + Note that because ``**configuration`` is passed using ``name=value`` syntax, + possible equal signs in other arguments passed to `Run Process` and + `Start Process` must be escaped with a backslash like ``name\\=value``. + See `Run Process` for an example. + + == Running processes in shell == + + The ``shell`` argument specifies whether to run the process in a shell or + not. By default shell is not used, which means that shell specific commands, + like ``copy`` and ``dir`` on Windows, are not available. You can, however, + run shell scripts and batch files without using a shell. + + Giving the ``shell`` argument any non-false value, such as ``shell=True``, + changes the program to be executed in a shell. It allows using the shell + capabilities, but can also make the process invocation operating system + dependent. Having a shell between the actually started process and this + library can also interfere communication with the process such as stopping + it and reading its outputs. Because of these problems, it is recommended + to use the shell only when absolutely necessary. + + When using a shell it is possible to give the whole command to execute + as a single string. See `Specifying command and arguments` section for + examples and more details in general. + + == Current working directory == + + By default the child process will be executed in the same directory + as the parent process, the process running tests, is executed. This + can be changed by giving an alternative location using the ``cwd`` argument. + Forward slashes in the given path are automatically converted to + backslashes on Windows. + + `Standard output and error streams`, when redirected to files, + are also relative to the current working directory possibly set using + the ``cwd`` argument. + + Example: + | `Run Process` | prog.exe | cwd=${ROOT}/directory | stdout=stdout.txt | + + == Environment variables == + + By default the child process will get a copy of the parent process's + environment variables. The ``env`` argument can be used to give the + child a custom environment as a Python dictionary. If there is a need + to specify only certain environment variable, it is possible to use the + ``env:=`` format to set or override only that named variables. + It is also possible to use these two approaches together. + + Examples: + | `Run Process` | program | env=${environ} | + | `Run Process` | program | env:http_proxy=10.144.1.10:8080 | env:PATH=%{PATH}${:}${PROGDIR} | + | `Run Process` | program | env=${environ} | env:EXTRA=value | + + == Standard output and error streams == + + By default processes are run so that their standard output and standard + error streams are kept in the memory. This works fine normally, + but if there is a lot of output, the output buffers may get full and + the program can hang. Additionally on Jython, everything written to + these in-memory buffers can be lost if the process is terminated. + + To avoid the above mentioned problems, it is possible to use ``stdout`` + and ``stderr`` arguments to specify files on the file system where to + redirect the outputs. This can also be useful if other processes or + other keywords need to read or manipulate the outputs somehow. + + Given ``stdout`` and ``stderr`` paths are relative to the `current working + directory`. Forward slashes in the given paths are automatically converted + to backslashes on Windows. + + As a special feature, it is possible to redirect the standard error to + the standard output by using ``stderr=STDOUT``. + + Regardless are outputs redirected to files or not, they are accessible + through the `result object` returned when the process ends. Commands are + expected to write outputs using the console encoding, but `output encoding` + can be configured using the ``output_encoding`` argument if needed. + + If you are not interested in outputs at all, you can explicitly ignore them + by using a special value ``DEVNULL`` both with ``stdout`` and ``stderr``. For + example, ``stdout=DEVNULL`` is the same as redirecting output on console + with ``> /dev/null`` on UNIX-like operating systems or ``> NUL`` on Windows. + This way the process will not hang even if there would be a lot of output, + but naturally output is not available after execution either. + + Support for the special value ``DEVNULL`` is new in Robot Framework 3.2. + + Examples: + | ${result} = | `Run Process` | program | stdout=${TEMPDIR}/stdout.txt | stderr=${TEMPDIR}/stderr.txt | + | `Log Many` | stdout: ${result.stdout} | stderr: ${result.stderr} | + | ${result} = | `Run Process` | program | stderr=STDOUT | + | `Log` | all output: ${result.stdout} | + | ${result} = | `Run Process` | program | stdout=DEVNULL | stderr=DEVNULL | + + Note that the created output files are not automatically removed after + the test run. The user is responsible to remove them if needed. + + == Output encoding == + + Executed commands are, by default, expected to write outputs to the + `standard output and error streams` using the encoding used by the + system console. If the command uses some other encoding, that can be + configured using the ``output_encoding`` argument. This is especially + useful on Windows where the console uses a different encoding than rest + of the system, and many commands use the general system encoding instead + of the console encoding. + + The value used with the ``output_encoding`` argument must be a valid + encoding and must match the encoding actually used by the command. As a + convenience, it is possible to use strings ``CONSOLE`` and ``SYSTEM`` + to specify that the console or system encoding is used, respectively. + If produced outputs use different encoding then configured, values got + through the `result object` will be invalid. + + Examples: + | `Start Process` | program | output_encoding=UTF-8 | + | `Run Process` | program | stdout=${path} | output_encoding=SYSTEM | + + The support to set output encoding is new in Robot Framework 3.0. + + == Alias == + + A custom name given to the process that can be used when selecting the + `active process`. + + Examples: + | `Start Process` | program | alias=example | + | `Run Process` | python | -c | print 'hello' | alias=hello | + + = Active process = + + The test library keeps record which of the started processes is currently + active. By default it is latest process started with `Start Process`, + but `Switch Process` can be used to select a different one. Using + `Run Process` does not affect the active process. + + The keywords that operate on started processes will use the active process + by default, but it is possible to explicitly select a different process + using the ``handle`` argument. The handle can be the identifier returned by + `Start Process` or an ``alias`` explicitly given to `Start Process` or + `Run Process`. + + = Result object = + + `Run Process`, `Wait For Process` and `Terminate Process` keywords return a + result object that contains information about the process execution as its + attributes. The same result object, or some of its attributes, can also + be get using `Get Process Result` keyword. Attributes available in the + object are documented in the table below. + + | = Attribute = | = Explanation = | + | rc | Return code of the process as an integer. | + | stdout | Contents of the standard output stream. | + | stderr | Contents of the standard error stream. | + | stdout_path | Path where stdout was redirected or ``None`` if not redirected. | + | stderr_path | Path where stderr was redirected or ``None`` if not redirected. | + + Example: + | ${result} = | `Run Process` | program | + | `Should Be Equal As Integers` | ${result.rc} | 0 | + | `Should Match` | ${result.stdout} | Some t?xt* | + | `Should Be Empty` | ${result.stderr} | | + | ${stdout} = | `Get File` | ${result.stdout_path} | + | `Should Be Equal` | ${stdout} | ${result.stdout} | + | `File Should Be Empty` | ${result.stderr_path} | | + + = Boolean arguments = + + Some keywords accept arguments that are handled as Boolean values true or + false. If such an argument is given as a string, it is considered false if + it is an empty string or equal to ``FALSE``, ``NONE``, ``NO``, ``OFF`` or + ``0``, case-insensitively. Other strings are considered true regardless + their value, and other argument types are tested using the same + [http://docs.python.org/library/stdtypes.html#truth|rules as in Python]. + + True examples: + | `Terminate Process` | kill=True | # Strings are generally true. | + | `Terminate Process` | kill=yes | # Same as the above. | + | `Terminate Process` | kill=${TRUE} | # Python ``True`` is true. | + | `Terminate Process` | kill=${42} | # Numbers other than 0 are true. | + + False examples: + | `Terminate Process` | kill=False | # String ``false`` is false. | + | `Terminate Process` | kill=no | # Also string ``no`` is false. | + | `Terminate Process` | kill=${EMPTY} | # Empty string is false. | + | `Terminate Process` | kill=${FALSE} | # Python ``False`` is false. | + + Considering string ``NONE`` false is new in Robot Framework 3.0.3 and + considering also ``OFF`` and ``0`` false is new in Robot Framework 3.1. + + = Example = + + | ***** Settings ***** + | Library Process + | Suite Teardown `Terminate All Processes` kill=True + | + | ***** Test Cases ***** + | Example + | `Start Process` program arg1 arg2 alias=First + | ${handle} = `Start Process` command.sh arg | command2.sh shell=True cwd=/path + | ${result} = `Run Process` ${CURDIR}/script.py + | `Should Not Contain` ${result.stdout} FAIL + | `Terminate Process` ${handle} + | ${result} = `Wait For Process` First + | `Should Be Equal As Integers` ${result.rc} 0 + """ + ROBOT_LIBRARY_SCOPE = 'GLOBAL' + ROBOT_LIBRARY_VERSION = get_version() + TERMINATE_TIMEOUT = 30 + KILL_TIMEOUT = 10 + + def __init__(self): + self._processes = ConnectionCache('No active process.') + self._results = {} + + def run_process(self, command, *arguments, **configuration): + """Runs a process and waits for it to complete. + + ``command`` and ``*arguments`` specify the command to execute and + arguments passed to it. See `Specifying command and arguments` for + more details. + + ``**configuration`` contains additional configuration related to + starting processes and waiting for them to finish. See `Process + configuration` for more details about configuration related to starting + processes. Configuration related to waiting for processes consists of + ``timeout`` and ``on_timeout`` arguments that have same semantics as + with `Wait For Process` keyword. By default there is no timeout, and + if timeout is defined the default action on timeout is ``terminate``. + + Returns a `result object` containing information about the execution. + + Note that possible equal signs in ``*arguments`` must be escaped + with a backslash (e.g. ``name\\=value``) to avoid them to be passed in + as ``**configuration``. + + Examples: + | ${result} = | Run Process | python | -c | print 'Hello, world!' | + | Should Be Equal | ${result.stdout} | Hello, world! | + | ${result} = | Run Process | ${command} | stderr=STDOUT | timeout=10s | + | ${result} = | Run Process | ${command} | timeout=1min | on_timeout=continue | + | ${result} = | Run Process | java -Dname\\=value Example | shell=True | cwd=${EXAMPLE} | + + This keyword does not change the `active process`. + """ + current = self._processes.current + timeout = configuration.pop('timeout', None) + on_timeout = configuration.pop('on_timeout', 'terminate') + try: + handle = self.start_process(command, *arguments, **configuration) + return self.wait_for_process(handle, timeout, on_timeout) + finally: + self._processes.current = current + + def start_process(self, command, *arguments, **configuration): + """Starts a new process on background. + + See `Specifying command and arguments` and `Process configuration` + for more information about the arguments, and `Run Process` keyword + for related examples. + + Makes the started process new `active process`. Returns an identifier + that can be used as a handle to activate the started process if needed. + + Processes are started so that they create a new process group. This + allows sending signals to and terminating also possible child + processes. This is not supported on Jython. + """ + conf = ProcessConfiguration(**configuration) + command = conf.get_command(command, list(arguments)) + self._log_start(command, conf) + process = subprocess.Popen(command, **conf.popen_config) + self._results[process] = ExecutionResult(process, **conf.result_config) + return self._processes.register(process, alias=conf.alias) + + def _log_start(self, command, config): + if is_list_like(command): + command = self.join_command_line(command) + logger.info(u'Starting process:\n%s' % system_decode(command)) + logger.debug(u'Process configuration:\n%s' % config) + + def is_process_running(self, handle=None): + """Checks is the process running or not. + + If ``handle`` is not given, uses the current `active process`. + + Returns ``True`` if the process is still running and ``False`` otherwise. + """ + return self._processes[handle].poll() is None + + def process_should_be_running(self, handle=None, + error_message='Process is not running.'): + """Verifies that the process is running. + + If ``handle`` is not given, uses the current `active process`. + + Fails if the process has stopped. + """ + if not self.is_process_running(handle): + raise AssertionError(error_message) + + def process_should_be_stopped(self, handle=None, + error_message='Process is running.'): + """Verifies that the process is not running. + + If ``handle`` is not given, uses the current `active process`. + + Fails if the process is still running. + """ + if self.is_process_running(handle): + raise AssertionError(error_message) + + def wait_for_process(self, handle=None, timeout=None, on_timeout='continue'): + """Waits for the process to complete or to reach the given timeout. + + The process to wait for must have been started earlier with + `Start Process`. If ``handle`` is not given, uses the current + `active process`. + + ``timeout`` defines the maximum time to wait for the process. It can be + given in + [http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#time-format| + various time formats] supported by Robot Framework, for example, ``42``, + ``42 s``, or ``1 minute 30 seconds``. The timeout is ignored if it is + Python ``None`` (default), string ``NONE`` (case-insensitively), zero, + or negative. + + ``on_timeout`` defines what to do if the timeout occurs. Possible values + and corresponding actions are explained in the table below. Notice + that reaching the timeout never fails the test. + + | = Value = | = Action = | + | continue | The process is left running (default). | + | terminate | The process is gracefully terminated. | + | kill | The process is forcefully stopped. | + + See `Terminate Process` keyword for more details how processes are + terminated and killed. + + If the process ends before the timeout or it is terminated or killed, + this keyword returns a `result object` containing information about + the execution. If the process is left running, Python ``None`` is + returned instead. + + Examples: + | # Process ends cleanly | | | + | ${result} = | Wait For Process | example | + | Process Should Be Stopped | example | | + | Should Be Equal As Integers | ${result.rc} | 0 | + | # Process does not end | | | + | ${result} = | Wait For Process | timeout=42 secs | + | Process Should Be Running | | | + | Should Be Equal | ${result} | ${NONE} | + | # Kill non-ending process | | | + | ${result} = | Wait For Process | timeout=1min 30s | on_timeout=kill | + | Process Should Be Stopped | | | + | Should Be Equal As Integers | ${result.rc} | -9 | + + Ignoring timeout if it is string ``NONE``, zero, or negative is new + in Robot Framework 3.2. + """ + process = self._processes[handle] + logger.info('Waiting for process to complete.') + timeout = self._get_timeout(timeout) + if timeout > 0: + if not self._process_is_stopped(process, timeout): + logger.info('Process did not complete in %s.' + % secs_to_timestr(timeout)) + return self._manage_process_timeout(handle, on_timeout.lower()) + return self._wait(process) + + def _get_timeout(self, timeout): + if (is_string(timeout) and timeout.upper() == 'NONE') or not timeout: + return -1 + return timestr_to_secs(timeout) + + def _manage_process_timeout(self, handle, on_timeout): + if on_timeout == 'terminate': + return self.terminate_process(handle) + elif on_timeout == 'kill': + return self.terminate_process(handle, kill=True) + else: + logger.info('Leaving process intact.') + return None + + def _wait(self, process): + result = self._results[process] + result.rc = process.wait() or 0 + result.close_streams() + logger.info('Process completed.') + return result + + def terminate_process(self, handle=None, kill=False): + """Stops the process gracefully or forcefully. + + If ``handle`` is not given, uses the current `active process`. + + By default first tries to stop the process gracefully. If the process + does not stop in 30 seconds, or ``kill`` argument is given a true value, + (see `Boolean arguments`) kills the process forcefully. Stops also all + the child processes of the originally started process. + + Waits for the process to stop after terminating it. Returns a `result + object` containing information about the execution similarly as `Wait + For Process`. + + On Unix-like machines graceful termination is done using ``TERM (15)`` + signal and killing using ``KILL (9)``. Use `Send Signal To Process` + instead if you just want to send either of these signals without + waiting for the process to stop. + + On Windows graceful termination is done using ``CTRL_BREAK_EVENT`` + event and killing using Win32 API function ``TerminateProcess()``. + + Examples: + | ${result} = | Terminate Process | | + | Should Be Equal As Integers | ${result.rc} | -15 | # On Unixes | + | Terminate Process | myproc | kill=true | + + Limitations: + - Graceful termination is not supported on Windows when using Jython. + Process is killed instead. + - Stopping the whole process group is not supported when using Jython. + - On Windows forceful kill only stops the main process, not possible + child processes. + """ + process = self._processes[handle] + if not hasattr(process, 'terminate'): + raise RuntimeError('Terminating processes is not supported ' + 'by this Python version.') + terminator = self._kill if is_truthy(kill) else self._terminate + try: + terminator(process) + except OSError: + if not self._process_is_stopped(process, self.KILL_TIMEOUT): + raise + logger.debug('Ignored OSError because process was stopped.') + return self._wait(process) + + def _kill(self, process): + logger.info('Forcefully killing process.') + if hasattr(os, 'killpg'): + os.killpg(process.pid, signal_module.SIGKILL) + else: + process.kill() + if not self._process_is_stopped(process, self.KILL_TIMEOUT): + raise RuntimeError('Failed to kill process.') + + def _terminate(self, process): + logger.info('Gracefully terminating process.') + # Sends signal to the whole process group both on POSIX and on Windows + # if supported by the interpreter. + if hasattr(os, 'killpg'): + os.killpg(process.pid, signal_module.SIGTERM) + elif hasattr(signal_module, 'CTRL_BREAK_EVENT'): + if IRONPYTHON: + # https://ironpython.codeplex.com/workitem/35020 + ctypes.windll.kernel32.GenerateConsoleCtrlEvent( + signal_module.CTRL_BREAK_EVENT, process.pid) + else: + process.send_signal(signal_module.CTRL_BREAK_EVENT) + else: + process.terminate() + if not self._process_is_stopped(process, self.TERMINATE_TIMEOUT): + logger.info('Graceful termination failed.') + self._kill(process) + + def terminate_all_processes(self, kill=False): + """Terminates all still running processes started by this library. + + This keyword can be used in suite teardown or elsewhere to make + sure that all processes are stopped, + + By default tries to terminate processes gracefully, but can be + configured to forcefully kill them immediately. See `Terminate Process` + that this keyword uses internally for more details. + """ + for handle in range(1, len(self._processes) + 1): + if self.is_process_running(handle): + self.terminate_process(handle, kill=kill) + self.__init__() + + def send_signal_to_process(self, signal, handle=None, group=False): + """Sends the given ``signal`` to the specified process. + + If ``handle`` is not given, uses the current `active process`. + + Signal can be specified either as an integer as a signal name. In the + latter case it is possible to give the name both with or without ``SIG`` + prefix, but names are case-sensitive. For example, all the examples + below send signal ``INT (2)``: + + | Send Signal To Process | 2 | | # Send to active process | + | Send Signal To Process | INT | | | + | Send Signal To Process | SIGINT | myproc | # Send to named process | + + This keyword is only supported on Unix-like machines, not on Windows. + What signals are supported depends on the system. For a list of + existing signals on your system, see the Unix man pages related to + signal handling (typically ``man signal`` or ``man 7 signal``). + + By default sends the signal only to the parent process, not to possible + child processes started by it. Notice that when `running processes in + shell`, the shell is the parent process and it depends on the system + does the shell propagate the signal to the actual started process. + + To send the signal to the whole process group, ``group`` argument can + be set to any true value (see `Boolean arguments`). This is not + supported by Jython, however. + """ + if os.sep == '\\': + raise RuntimeError('This keyword does not work on Windows.') + process = self._processes[handle] + signum = self._get_signal_number(signal) + logger.info('Sending signal %s (%d).' % (signal, signum)) + if is_truthy(group) and hasattr(os, 'killpg'): + os.killpg(process.pid, signum) + elif hasattr(process, 'send_signal'): + process.send_signal(signum) + else: + raise RuntimeError('Sending signals is not supported ' + 'by this Python version.') + + def _get_signal_number(self, int_or_name): + try: + return int(int_or_name) + except ValueError: + return self._convert_signal_name_to_number(int_or_name) + + def _convert_signal_name_to_number(self, name): + try: + return getattr(signal_module, + name if name.startswith('SIG') else 'SIG' + name) + except AttributeError: + raise RuntimeError("Unsupported signal '%s'." % name) + + def get_process_id(self, handle=None): + """Returns the process ID (pid) of the process as an integer. + + If ``handle`` is not given, uses the current `active process`. + + Notice that the pid is not the same as the handle returned by + `Start Process` that is used internally by this library. + """ + return self._processes[handle].pid + + def get_process_object(self, handle=None): + """Return the underlying ``subprocess.Popen`` object. + + If ``handle`` is not given, uses the current `active process`. + """ + return self._processes[handle] + + def get_process_result(self, handle=None, rc=False, stdout=False, + stderr=False, stdout_path=False, stderr_path=False): + """Returns the specified `result object` or some of its attributes. + + The given ``handle`` specifies the process whose results should be + returned. If no ``handle`` is given, results of the current `active + process` are returned. In either case, the process must have been + finishes before this keyword can be used. In practice this means + that processes started with `Start Process` must be finished either + with `Wait For Process` or `Terminate Process` before using this + keyword. + + If no other arguments than the optional ``handle`` are given, a whole + `result object` is returned. If one or more of the other arguments + are given any true value, only the specified attributes of the + `result object` are returned. These attributes are always returned + in the same order as arguments are specified in the keyword signature. + See `Boolean arguments` section for more details about true and false + values. + + Examples: + | Run Process | python | -c | print 'Hello, world!' | alias=myproc | + | # Get result object | | | + | ${result} = | Get Process Result | myproc | + | Should Be Equal | ${result.rc} | ${0} | + | Should Be Equal | ${result.stdout} | Hello, world! | + | Should Be Empty | ${result.stderr} | | + | # Get one attribute | | | + | ${stdout} = | Get Process Result | myproc | stdout=true | + | Should Be Equal | ${stdout} | Hello, world! | + | # Multiple attributes | | | + | ${stdout} | ${stderr} = | Get Process Result | myproc | stdout=yes | stderr=yes | + | Should Be Equal | ${stdout} | Hello, world! | + | Should Be Empty | ${stderr} | | + + Although getting results of a previously executed process can be handy + in general, the main use case for this keyword is returning results + over the remote library interface. The remote interface does not + support returning the whole result object, but individual attributes + can be returned without problems. + """ + result = self._results[self._processes[handle]] + if result.rc is None: + raise RuntimeError('Getting results of unfinished processes ' + 'is not supported.') + attributes = self._get_result_attributes(result, rc, stdout, stderr, + stdout_path, stderr_path) + if not attributes: + return result + elif len(attributes) == 1: + return attributes[0] + return attributes + + def _get_result_attributes(self, result, *includes): + attributes = (result.rc, result.stdout, result.stderr, + result.stdout_path, result.stderr_path) + includes = (is_truthy(incl) for incl in includes) + return tuple(attr for attr, incl in zip(attributes, includes) if incl) + + def switch_process(self, handle): + """Makes the specified process the current `active process`. + + The handle can be an identifier returned by `Start Process` or + the ``alias`` given to it explicitly. + + Example: + | Start Process | prog1 | alias=process1 | + | Start Process | prog2 | alias=process2 | + | # currently active process is process2 | + | Switch Process | process1 | + | # now active process is process1 | + """ + self._processes.switch(handle) + + def _process_is_stopped(self, process, timeout): + stopped = lambda: process.poll() is not None + max_time = time.time() + timeout + while time.time() <= max_time and not stopped(): + time.sleep(min(0.1, timeout)) + return stopped() + + def split_command_line(self, args, escaping=False): + """Splits command line string into a list of arguments. + + String is split from spaces, but argument surrounded in quotes may + contain spaces in them. If ``escaping`` is given a true value, then + backslash is treated as an escape character. It can escape unquoted + spaces, quotes inside quotes, and so on, but it also requires using + double backslashes when using Windows paths. + + Examples: + | @{cmd} = | Split Command Line | --option "value with spaces" | + | Should Be True | $cmd == ['--option', 'value with spaces'] | + + New in Robot Framework 2.9.2. + """ + return cmdline2list(args, escaping=escaping) + + def join_command_line(self, *args): + """Joins arguments into one command line string. + + In resulting command line string arguments are delimited with a space, + arguments containing spaces are surrounded with quotes, and possible + quotes are escaped with a backslash. + + If this keyword is given only one argument and that is a list like + object, then the values of that list are joined instead. + + Example: + | ${cmd} = | Join Command Line | --option | value with spaces | + | Should Be Equal | ${cmd} | --option "value with spaces" | + + New in Robot Framework 2.9.2. + """ + if len(args) == 1 and is_list_like(args[0]): + args = args[0] + return subprocess.list2cmdline(args) + + +class ExecutionResult(object): + + def __init__(self, process, stdout, stderr, rc=None, output_encoding=None): + self._process = process + self.stdout_path = self._get_path(stdout) + self.stderr_path = self._get_path(stderr) + self.rc = rc + self._output_encoding = output_encoding + self._stdout = None + self._stderr = None + self._custom_streams = [stream for stream in (stdout, stderr) + if self._is_custom_stream(stream)] + + def _get_path(self, stream): + return stream.name if self._is_custom_stream(stream) else None + + def _is_custom_stream(self, stream): + return stream not in (subprocess.PIPE, subprocess.STDOUT) + + @property + def stdout(self): + if self._stdout is None: + self._read_stdout() + return self._stdout + + @property + def stderr(self): + if self._stderr is None: + self._read_stderr() + return self._stderr + + def _read_stdout(self): + self._stdout = self._read_stream(self.stdout_path, self._process.stdout) + + def _read_stderr(self): + self._stderr = self._read_stream(self.stderr_path, self._process.stderr) + + def _read_stream(self, stream_path, stream): + if stream_path: + stream = open(stream_path, 'rb') + elif not self._is_open(stream): + return '' + try: + content = stream.read() + except IOError: # http://bugs.jython.org/issue2218 + return '' + finally: + if stream_path: + stream.close() + return self._format_output(content) + + def _is_open(self, stream): + return stream and not stream.closed + + def _format_output(self, output): + output = console_decode(output, self._output_encoding, force=True) + output = output.replace('\r\n', '\n') + if output.endswith('\n'): + output = output[:-1] + return output + + def close_streams(self): + standard_streams = self._get_and_read_standard_streams(self._process) + for stream in standard_streams + self._custom_streams: + if self._is_open(stream): + stream.close() + + def _get_and_read_standard_streams(self, process): + stdin, stdout, stderr = process.stdin, process.stdout, process.stderr + if stdout: + self._read_stdout() + if stderr: + self._read_stderr() + return [stdin, stdout, stderr] + + def __str__(self): + return '' % self.rc + + +@py2to3 +class ProcessConfiguration(object): + + def __init__(self, cwd=None, shell=False, stdout=None, stderr=None, + output_encoding='CONSOLE', alias=None, env=None, **rest): + self.cwd = self._get_cwd(cwd) + self.stdout_stream = self._new_stream(stdout) + self.stderr_stream = self._get_stderr(stderr, stdout, self.stdout_stream) + self.shell = is_truthy(shell) + self.alias = alias + self.output_encoding = output_encoding + self.env = self._construct_env(env, rest) + + def _get_cwd(self, cwd): + if cwd: + return cwd.replace('/', os.sep) + return abspath('.') + + def _new_stream(self, name): + if name == 'DEVNULL': + return open(os.devnull, 'w') + if name: + name = name.replace('/', os.sep) + return open(os.path.join(self.cwd, name), 'w') + return subprocess.PIPE + + def _get_stderr(self, stderr, stdout, stdout_stream): + if stderr and stderr in ['STDOUT', stdout]: + if stdout_stream != subprocess.PIPE: + return stdout_stream + return subprocess.STDOUT + return self._new_stream(stderr) + + def _construct_env(self, env, extra): + env = self._get_initial_env(env, extra) + if env is None: + return None + if WINDOWS: + env = NormalizedDict(env, spaceless=False) + self._add_to_env(env, extra) + if WINDOWS: + env = dict((key.upper(), env[key]) for key in env) + return env + + def _get_initial_env(self, env, extra): + if env: + return dict((system_encode(k), system_encode(env[k])) for k in env) + if extra: + return os.environ.copy() + return None + + def _add_to_env(self, env, extra): + for key in extra: + if not key.startswith('env:'): + raise RuntimeError("Keyword argument '%s' is not supported by " + "this keyword." % key) + env[system_encode(key[4:])] = system_encode(extra[key]) + + def get_command(self, command, arguments): + command = [system_encode(item) for item in [command] + arguments] + if not self.shell: + return command + if arguments: + return subprocess.list2cmdline(command) + return command[0] + + @property + def popen_config(self): + config = {'stdout': self.stdout_stream, + 'stderr': self.stderr_stream, + 'stdin': subprocess.PIPE, + 'shell': self.shell, + 'cwd': self.cwd, + 'env': self.env} + # Close file descriptors regardless the Python version: + # https://github.com/robotframework/robotframework/issues/2794 + if not WINDOWS: + config['close_fds'] = True + if not JYTHON: + self._add_process_group_config(config) + return config + + def _add_process_group_config(self, config): + if hasattr(os, 'setsid'): + config['preexec_fn'] = os.setsid + if hasattr(subprocess, 'CREATE_NEW_PROCESS_GROUP'): + config['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP + + @property + def result_config(self): + return {'stdout': self.stdout_stream, + 'stderr': self.stderr_stream, + 'output_encoding': self.output_encoding} + + def __unicode__(self): + return """\ +cwd: %s +shell: %s +stdout: %s +stderr: %s +alias: %s +env: %s""" % (self.cwd, self.shell, self._stream_name(self.stdout_stream), + self._stream_name(self.stderr_stream), self.alias, self.env) + + def _stream_name(self, stream): + if hasattr(stream, 'name'): + return stream.name + return {subprocess.PIPE: 'PIPE', + subprocess.STDOUT: 'STDOUT'}.get(stream, stream) diff --git a/robot/lib/python3.8/site-packages/robot/libraries/Remote.py b/robot/lib/python3.8/site-packages/robot/libraries/Remote.py new file mode 100644 index 0000000000000000000000000000000000000000..f8dea68ab00a568bfba871fd207ca2fb4115ecd9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/libraries/Remote.py @@ -0,0 +1,298 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import + +from contextlib import contextmanager + +try: + import httplib + import xmlrpclib +except ImportError: # Py3 + import http.client as httplib + import xmlrpc.client as xmlrpclib +import re +import socket +import sys +import time + +try: + from xml.parsers.expat import ExpatError +except ImportError: # No expat in IronPython 2.7 + class ExpatError(Exception): + pass + +from robot.errors import RemoteError +from robot.utils import (is_bytes, is_dict_like, is_list_like, is_number, + is_string, timestr_to_secs, unic, DotDict, + IRONPYTHON, JYTHON, PY2) + + +class Remote(object): + ROBOT_LIBRARY_SCOPE = 'TEST SUITE' + + def __init__(self, uri='http://127.0.0.1:8270', timeout=None): + """Connects to a remote server at ``uri``. + + Optional ``timeout`` can be used to specify a timeout to wait when + initially connecting to the server and if a connection accidentally + closes. Timeout can be given as seconds (e.g. ``60``) or using + Robot Framework time format (e.g. ``60s``, ``2 minutes 10 seconds``). + + The default timeout is typically several minutes, but it depends on + the operating system and its configuration. Notice that setting + a timeout that is shorter than keyword execution time will interrupt + the keyword. + + Timeouts do not work with IronPython. + """ + if '://' not in uri: + uri = 'http://' + uri + if timeout: + timeout = timestr_to_secs(timeout) + self._uri = uri + self._client = XmlRpcRemoteClient(uri, timeout) + + def get_keyword_names(self, attempts=2): + for i in range(attempts): + time.sleep(i) + try: + return self._client.get_keyword_names() + except TypeError as err: + error = err + raise RuntimeError('Connecting remote server at %s failed: %s' + % (self._uri, error)) + + def get_keyword_arguments(self, name): + try: + return self._client.get_keyword_arguments(name) + except TypeError: + return ['*args'] + + def get_keyword_types(self, name): + try: + return self._client.get_keyword_types(name) + except TypeError: + return None + + def get_keyword_tags(self, name): + try: + return self._client.get_keyword_tags(name) + except TypeError: + return None + + def get_keyword_documentation(self, name): + try: + return self._client.get_keyword_documentation(name) + except TypeError: + return None + + def run_keyword(self, name, args, kwargs): + coercer = ArgumentCoercer() + args = coercer.coerce(args) + kwargs = coercer.coerce(kwargs) + result = RemoteResult(self._client.run_keyword(name, args, kwargs)) + sys.stdout.write(result.output) + if result.status != 'PASS': + raise RemoteError(result.error, result.traceback, result.fatal, + result.continuable) + return result.return_ + + +class ArgumentCoercer(object): + binary = re.compile('[\x00-\x08\x0B\x0C\x0E-\x1F]') + non_ascii = re.compile('[\x80-\xff]') + + def coerce(self, argument): + for handles, handler in [(is_string, self._handle_string), + (is_bytes, self._handle_bytes), + (is_number, self._pass_through), + (is_dict_like, self._coerce_dict), + (is_list_like, self._coerce_list), + (lambda arg: True, self._to_string)]: + if handles(argument): + return handler(argument) + + def _handle_string(self, arg): + if self._string_contains_binary(arg): + return self._handle_binary_in_string(arg) + return arg + + def _string_contains_binary(self, arg): + return (self.binary.search(arg) or + is_bytes(arg) and self.non_ascii.search(arg)) + + def _handle_binary_in_string(self, arg): + try: + if not is_bytes(arg): + arg = arg.encode('ASCII') + except UnicodeError: + raise ValueError('Cannot represent %r as binary.' % arg) + return xmlrpclib.Binary(arg) + + def _handle_bytes(self, arg): + # http://bugs.jython.org/issue2429 + if IRONPYTHON or JYTHON: + arg = str(arg) + return xmlrpclib.Binary(arg) + + def _pass_through(self, arg): + return arg + + def _coerce_list(self, arg): + return [self.coerce(item) for item in arg] + + def _coerce_dict(self, arg): + return dict((self._to_key(key), self.coerce(arg[key])) for key in arg) + + def _to_key(self, item): + item = self._to_string(item) + self._validate_key(item) + return item + + def _to_string(self, item): + item = unic(item) if item is not None else '' + return self._handle_string(item) + + def _validate_key(self, key): + if isinstance(key, xmlrpclib.Binary): + raise ValueError('Dictionary keys cannot be binary. Got %s%r.' + % ('b' if PY2 else '', key.data)) + if IRONPYTHON: + try: + key.encode('ASCII') + except UnicodeError: + raise ValueError('Dictionary keys cannot contain non-ASCII ' + 'characters on IronPython. Got %r.' % key) + + +class RemoteResult(object): + + def __init__(self, result): + if not (is_dict_like(result) and 'status' in result): + raise RuntimeError('Invalid remote result dictionary: %s' % result) + self.status = result['status'] + self.output = unic(self._get(result, 'output')) + self.return_ = self._get(result, 'return') + self.error = unic(self._get(result, 'error')) + self.traceback = unic(self._get(result, 'traceback')) + self.fatal = bool(self._get(result, 'fatal', False)) + self.continuable = bool(self._get(result, 'continuable', False)) + + def _get(self, result, key, default=''): + value = result.get(key, default) + return self._convert(value) + + def _convert(self, value): + if isinstance(value, xmlrpclib.Binary): + return bytes(value.data) + if is_dict_like(value): + return DotDict((k, self._convert(v)) for k, v in value.items()) + if is_list_like(value): + return [self._convert(v) for v in value] + return value + + +class XmlRpcRemoteClient(object): + + def __init__(self, uri, timeout=None): + self.uri = uri + self.timeout = timeout + + @property + @contextmanager + def _server(self): + if self.uri.startswith('https://'): + transport = TimeoutHTTPSTransport(timeout=self.timeout) + else: + transport = TimeoutHTTPTransport(timeout=self.timeout) + server = xmlrpclib.ServerProxy(self.uri, encoding='UTF-8', + transport=transport) + try: + yield server + except (socket.error, xmlrpclib.Error) as err: + raise TypeError(err) + finally: + server('close')() + + def get_keyword_names(self): + with self._server as server: + return server.get_keyword_names() + + def get_keyword_arguments(self, name): + with self._server as server: + return server.get_keyword_arguments(name) + + def get_keyword_types(self, name): + with self._server as server: + return server.get_keyword_types(name) + + def get_keyword_tags(self, name): + with self._server as server: + return server.get_keyword_tags(name) + + def get_keyword_documentation(self, name): + with self._server as server: + return server.get_keyword_documentation(name) + + def run_keyword(self, name, args, kwargs): + with self._server as server: + run_keyword_args = [name, args, kwargs] if kwargs else [name, args] + try: + return server.run_keyword(*run_keyword_args) + except xmlrpclib.Fault as err: + message = err.faultString + except socket.error as err: + message = 'Connection to remote server broken: %s' % err + except ExpatError as err: + message = ('Processing XML-RPC return value failed. ' + 'Most often this happens when the return value ' + 'contains characters that are not valid in XML. ' + 'Original error was: ExpatError: %s' % err) + raise RuntimeError(message) + + +# Custom XML-RPC timeouts based on +# http://stackoverflow.com/questions/2425799/timeout-for-xmlrpclib-client-requests + +class TimeoutHTTPTransport(xmlrpclib.Transport): + _connection_class = httplib.HTTPConnection + + def __init__(self, use_datetime=0, timeout=None): + xmlrpclib.Transport.__init__(self, use_datetime) + if not timeout: + timeout = socket._GLOBAL_DEFAULT_TIMEOUT + self.timeout = timeout + + def make_connection(self, host): + if self._connection and host == self._connection[0]: + return self._connection[1] + chost, self._extra_headers, x509 = self.get_host_info(host) + self._connection = host, self._connection_class(chost, timeout=self.timeout) + return self._connection[1] + + +if IRONPYTHON: + + class TimeoutHTTPTransport(xmlrpclib.Transport): + + def __init__(self, use_datetime=0, timeout=None): + xmlrpclib.Transport.__init__(self, use_datetime) + if timeout: + raise RuntimeError('Timeouts are not supported on IronPython.') + + +class TimeoutHTTPSTransport(TimeoutHTTPTransport): + _connection_class = httplib.HTTPSConnection diff --git a/robot/lib/python3.8/site-packages/robot/libraries/Reserved.py b/robot/lib/python3.8/site-packages/robot/libraries/Reserved.py new file mode 100644 index 0000000000000000000000000000000000000000..7d6426eb53bab20c9c82c4385e862e9da1313504 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/libraries/Reserved.py @@ -0,0 +1,31 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RESERVED_KEYWORDS = ['for', 'while', 'break', 'continue', 'end', + 'if', 'else', 'elif', 'else if', 'return'] + + +class Reserved(object): + ROBOT_LIBRARY_SCOPE = 'GLOBAL' + + def get_keyword_names(self): + return RESERVED_KEYWORDS + + def run_keyword(self, name, args): + error = "'%s' is a reserved keyword." % name.title() + if name in ('else', 'else if'): + error += (" It must be in uppercase (%s) when used as a marker" + " with 'Run Keyword If'." % name.upper()) + raise Exception(error) diff --git a/robot/lib/python3.8/site-packages/robot/libraries/Screenshot.py b/robot/lib/python3.8/site-packages/robot/libraries/Screenshot.py new file mode 100644 index 0000000000000000000000000000000000000000..9f2745b5e6777446a226cd6b07cc95d8fbc4e675 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/libraries/Screenshot.py @@ -0,0 +1,397 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import os +import subprocess +import sys +if sys.platform.startswith('java'): + from java.awt import Toolkit, Robot, Rectangle + from javax.imageio import ImageIO + from java.io import File +elif sys.platform == 'cli': + import clr + clr.AddReference('System.Windows.Forms') + clr.AddReference('System.Drawing') + from System.Drawing import Bitmap, Graphics, Imaging + from System.Windows.Forms import Screen +else: + try: + import wx + wx_app = wx.App(False) # Linux Python 2.7 must exist on global scope + except ImportError: + wx = None + try: + from gtk import gdk + except ImportError: + gdk = None + try: + from PIL import ImageGrab # apparently available only on Windows + except ImportError: + ImageGrab = None + +from robot.api import logger +from robot.libraries.BuiltIn import BuiltIn +from robot.version import get_version +from robot.utils import abspath, get_error_message, get_link_path, py2to3 + + +class Screenshot(object): + """Test library for taking screenshots on the machine where tests are run. + + Notice that successfully taking screenshots requires tests to be run with + a physical or virtual display. + + == Table of contents == + + %TOC% + + = Using with Python = + + How screenshots are taken when using Python depends on the operating + system. On OSX screenshots are taken using the built-in ``screencapture`` + utility. On other operating systems you need to have one of the following + tools or Python modules installed. You can specify the tool/module to use + when `importing` the library. If no tool or module is specified, the first + one found will be used. + + - wxPython :: http://wxpython.org :: Required also by RIDE so many Robot + Framework users already have this module installed. + - PyGTK :: http://pygtk.org :: This module is available by default on most + Linux distributions. + - Pillow :: http://python-pillow.github.io :: + Only works on Windows. Also the original PIL package is supported. + - Scrot :: http://en.wikipedia.org/wiki/Scrot :: Not used on Windows. + Install with ``apt-get install scrot`` or similar. + + Using ``screencapture`` on OSX and specifying explicit screenshot module + are new in Robot Framework 2.9.2. The support for using ``scrot`` is new + in Robot Framework 3.0. + + = Using with Jython and IronPython = + + With Jython and IronPython this library uses APIs provided by JVM and .NET + platforms, respectively. These APIs are always available and thus no + external modules are needed. + + = Where screenshots are saved = + + By default screenshots are saved into the same directory where the Robot + Framework log file is written. If no log is created, screenshots are saved + into the directory where the XML output file is written. + + It is possible to specify a custom location for screenshots using + ``screenshot_directory`` argument when `importing` the library and + using `Set Screenshot Directory` keyword during execution. It is also + possible to save screenshots using an absolute path. + + = ScreenCapLibrary = + + [https://github.com/mihaiparvu/ScreenCapLibrary|ScreenCapLibrary] is an + external Robot Framework library that can be used as an alternative, + which additionally provides support for multiple formats, adjusting the + quality, using GIFs and video capturing. + """ + + ROBOT_LIBRARY_SCOPE = 'TEST SUITE' + ROBOT_LIBRARY_VERSION = get_version() + + def __init__(self, screenshot_directory=None, screenshot_module=None): + """Configure where screenshots are saved. + + If ``screenshot_directory`` is not given, screenshots are saved into + same directory as the log file. The directory can also be set using + `Set Screenshot Directory` keyword. + + ``screenshot_module`` specifies the module or tool to use when using + this library on Python outside OSX. Possible values are ``wxPython``, + ``PyGTK``, ``PIL`` and ``scrot``, case-insensitively. If no value is + given, the first module/tool found is used in that order. See `Using + with Python` for more information. + + Examples (use only one of these): + | =Setting= | =Value= | =Value= | + | Library | Screenshot | | + | Library | Screenshot | ${TEMPDIR} | + | Library | Screenshot | screenshot_module=PyGTK | + + Specifying explicit screenshot module is new in Robot Framework 2.9.2. + """ + self._given_screenshot_dir = self._norm_path(screenshot_directory) + self._screenshot_taker = ScreenshotTaker(screenshot_module) + + def _norm_path(self, path): + if not path: + return path + return os.path.normpath(path.replace('/', os.sep)) + + @property + def _screenshot_dir(self): + return self._given_screenshot_dir or self._log_dir + + @property + def _log_dir(self): + variables = BuiltIn().get_variables() + outdir = variables['${OUTPUTDIR}'] + log = variables['${LOGFILE}'] + log = os.path.dirname(log) if log != 'NONE' else '.' + return self._norm_path(os.path.join(outdir, log)) + + def set_screenshot_directory(self, path): + """Sets the directory where screenshots are saved. + + It is possible to use ``/`` as a path separator in all operating + systems. Path to the old directory is returned. + + The directory can also be set in `importing`. + """ + path = self._norm_path(path) + if not os.path.isdir(path): + raise RuntimeError("Directory '%s' does not exist." % path) + old = self._screenshot_dir + self._given_screenshot_dir = path + return old + + def take_screenshot(self, name="screenshot", width="800px"): + """Takes a screenshot in JPEG format and embeds it into the log file. + + Name of the file where the screenshot is stored is derived from the + given ``name``. If the ``name`` ends with extension ``.jpg`` or + ``.jpeg``, the screenshot will be stored with that exact name. + Otherwise a unique name is created by adding an underscore, a running + index and an extension to the ``name``. + + The name will be interpreted to be relative to the directory where + the log file is written. It is also possible to use absolute paths. + Using ``/`` as a path separator works in all operating systems. + + ``width`` specifies the size of the screenshot in the log file. + + Examples: (LOGDIR is determined automatically by the library) + | Take Screenshot | | | # LOGDIR/screenshot_1.jpg (index automatically incremented) | + | Take Screenshot | mypic | | # LOGDIR/mypic_1.jpg (index automatically incremented) | + | Take Screenshot | ${TEMPDIR}/mypic | | # /tmp/mypic_1.jpg (index automatically incremented) | + | Take Screenshot | pic.jpg | | # LOGDIR/pic.jpg (always uses this file) | + | Take Screenshot | images/login.jpg | 80% | # Specify both name and width. | + | Take Screenshot | width=550px | | # Specify only width. | + + The path where the screenshot is saved is returned. + """ + path = self._save_screenshot(name) + self._embed_screenshot(path, width) + return path + + def take_screenshot_without_embedding(self, name="screenshot"): + """Takes a screenshot and links it from the log file. + + This keyword is otherwise identical to `Take Screenshot` but the saved + screenshot is not embedded into the log file. The screenshot is linked + so it is nevertheless easily available. + """ + path = self._save_screenshot(name) + self._link_screenshot(path) + return path + + def _save_screenshot(self, basename, directory=None): + path = self._get_screenshot_path(basename, directory) + return self._screenshot_to_file(path) + + def _screenshot_to_file(self, path): + path = self._validate_screenshot_path(path) + logger.debug('Using %s module/tool for taking screenshot.' + % self._screenshot_taker.module) + try: + self._screenshot_taker(path) + except: + logger.warn('Taking screenshot failed: %s\n' + 'Make sure tests are run with a physical or virtual ' + 'display.' % get_error_message()) + return path + + def _validate_screenshot_path(self, path): + path = abspath(self._norm_path(path)) + if not os.path.exists(os.path.dirname(path)): + raise RuntimeError("Directory '%s' where to save the screenshot " + "does not exist" % os.path.dirname(path)) + return path + + def _get_screenshot_path(self, basename, directory): + directory = self._norm_path(directory) if directory else self._screenshot_dir + if basename.lower().endswith(('.jpg', '.jpeg')): + return os.path.join(directory, basename) + index = 0 + while True: + index += 1 + path = os.path.join(directory, "%s_%d.jpg" % (basename, index)) + if not os.path.exists(path): + return path + + def _embed_screenshot(self, path, width): + link = get_link_path(path, self._log_dir) + logger.info('' + % (link, link, width), html=True) + + def _link_screenshot(self, path): + link = get_link_path(path, self._log_dir) + logger.info("Screenshot saved to '%s'." + % (link, path), html=True) + + +@py2to3 +class ScreenshotTaker(object): + + def __init__(self, module_name=None): + self._screenshot = self._get_screenshot_taker(module_name) + self.module = self._screenshot.__name__.split('_')[1] + + def __call__(self, path): + self._screenshot(path) + + def __nonzero__(self): + return self.module != 'no' + + def test(self, path=None): + if not self: + print("Cannot take screenshots.") + return False + print("Using '%s' to take screenshot." % self.module) + if not path: + print("Not taking test screenshot.") + return True + print("Taking test screenshot to '%s'." % path) + try: + self(path) + except: + print("Failed: %s" % get_error_message()) + return False + else: + print("Success!") + return True + + def _get_screenshot_taker(self, module_name=None): + if sys.platform.startswith('java'): + return self._java_screenshot + if sys.platform == 'cli': + return self._cli_screenshot + if sys.platform == 'darwin': + return self._osx_screenshot + if module_name: + return self._get_named_screenshot_taker(module_name.lower()) + return self._get_default_screenshot_taker() + + def _get_named_screenshot_taker(self, name): + screenshot_takers = {'wxpython': (wx, self._wx_screenshot), + 'pygtk': (gdk, self._gtk_screenshot), + 'pil': (ImageGrab, self._pil_screenshot), + 'scrot': (self._scrot, self._scrot_screenshot)} + if name not in screenshot_takers: + raise RuntimeError("Invalid screenshot module or tool '%s'." % name) + supported, screenshot_taker = screenshot_takers[name] + if not supported: + raise RuntimeError("Screenshot module or tool '%s' not installed." + % name) + return screenshot_taker + + def _get_default_screenshot_taker(self): + for module, screenshot_taker in [(wx, self._wx_screenshot), + (gdk, self._gtk_screenshot), + (ImageGrab, self._pil_screenshot), + (self._scrot, self._scrot_screenshot), + (True, self._no_screenshot)]: + if module: + return screenshot_taker + + def _java_screenshot(self, path): + size = Toolkit.getDefaultToolkit().getScreenSize() + rectangle = Rectangle(0, 0, size.width, size.height) + image = Robot().createScreenCapture(rectangle) + ImageIO.write(image, 'jpg', File(path)) + + def _cli_screenshot(self, path): + bmp = Bitmap(Screen.PrimaryScreen.Bounds.Width, + Screen.PrimaryScreen.Bounds.Height) + graphics = Graphics.FromImage(bmp) + try: + graphics.CopyFromScreen(0, 0, 0, 0, bmp.Size) + finally: + graphics.Dispose() + bmp.Save(path, Imaging.ImageFormat.Jpeg) + + def _osx_screenshot(self, path): + if self._call('screencapture', '-t', 'jpg', path) != 0: + raise RuntimeError("Using 'screencapture' failed.") + + def _call(self, *command): + try: + return subprocess.call(command, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + except OSError: + return -1 + + @property + def _scrot(self): + return os.sep == '/' and self._call('scrot', '--version') == 0 + + def _scrot_screenshot(self, path): + if not path.endswith(('.jpg', '.jpeg')): + raise RuntimeError("Scrot requires extension to be '.jpg' or " + "'.jpeg', got '%s'." % os.path.splitext(path)[1]) + if os.path.exists(path): + os.remove(path) + if self._call('scrot', '--silent', path) != 0: + raise RuntimeError("Using 'scrot' failed.") + + def _wx_screenshot(self, path): + # depends on wx_app been created + context = wx.ScreenDC() + width, height = context.GetSize() + if wx.__version__ >= '4': + bitmap = wx.Bitmap(width, height, -1) + else: + bitmap = wx.EmptyBitmap(width, height, -1) + memory = wx.MemoryDC() + memory.SelectObject(bitmap) + memory.Blit(0, 0, width, height, context, -1, -1) + memory.SelectObject(wx.NullBitmap) + bitmap.SaveFile(path, wx.BITMAP_TYPE_JPEG) + + def _gtk_screenshot(self, path): + window = gdk.get_default_root_window() + if not window: + raise RuntimeError('Taking screenshot failed.') + width, height = window.get_size() + pb = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, width, height) + pb = pb.get_from_drawable(window, window.get_colormap(), + 0, 0, 0, 0, width, height) + if not pb: + raise RuntimeError('Taking screenshot failed.') + pb.save(path, 'jpeg') + + def _pil_screenshot(self, path): + ImageGrab.grab().save(path, 'JPEG') + + def _no_screenshot(self, path): + raise RuntimeError('Taking screenshots is not supported on this platform ' + 'by default. See library documentation for details.') + + +if __name__ == "__main__": + if len(sys.argv) not in [2, 3]: + sys.exit("Usage: %s |test [wx|pygtk|pil|scrot]" + % os.path.basename(sys.argv[0])) + path = sys.argv[1] if sys.argv[1] != 'test' else None + module = sys.argv[2] if len(sys.argv) > 2 else None + ScreenshotTaker(module).test(path) diff --git a/robot/lib/python3.8/site-packages/robot/libraries/String.py b/robot/lib/python3.8/site-packages/robot/libraries/String.py new file mode 100644 index 0000000000000000000000000000000000000000..c68d8f508f8f1ea52adcb3dba1e562a50d4fc706 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/libraries/String.py @@ -0,0 +1,780 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import + +import os +import re +from fnmatch import fnmatchcase +from random import randint +from string import ascii_lowercase, ascii_uppercase, digits + + +from robot.api import logger +from robot.utils import (is_bytes, is_string, is_truthy, is_unicode, lower, + unic, FileReader, PY3) +from robot.version import get_version + + +class String(object): + """A test library for string manipulation and verification. + + ``String`` is Robot Framework's standard library for manipulating + strings (e.g. `Replace String Using Regexp`, `Split To Lines`) and + verifying their contents (e.g. `Should Be String`). + + Following keywords from ``BuiltIn`` library can also be used with strings: + + - `Catenate` + - `Get Length` + - `Length Should Be` + - `Should (Not) Be Empty` + - `Should (Not) Be Equal (As Strings/Integers/Numbers)` + - `Should (Not) Match (Regexp)` + - `Should (Not) Contain` + - `Should (Not) Start With` + - `Should (Not) End With` + - `Convert To String` + - `Convert To Bytes` + """ + ROBOT_LIBRARY_SCOPE = 'GLOBAL' + ROBOT_LIBRARY_VERSION = get_version() + + def convert_to_lower_case(self, string): + """Converts string to lower case. + + Uses Python's standard + [https://docs.python.org/library/stdtypes.html#str.lower|lower()] + method. + + Examples: + | ${str1} = | Convert To Lower Case | ABC | + | ${str2} = | Convert To Lower Case | 1A2c3D | + | Should Be Equal | ${str1} | abc | + | Should Be Equal | ${str2} | 1a2c3d | + """ + # Custom `lower` needed due to IronPython bug. See its code and + # comments for more details. + return lower(string) + + def convert_to_upper_case(self, string): + """Converts string to upper case. + + Uses Python's standard + [https://docs.python.org/library/stdtypes.html#str.upper|upper()] + method. + + Examples: + | ${str1} = | Convert To Upper Case | abc | + | ${str2} = | Convert To Upper Case | 1a2C3d | + | Should Be Equal | ${str1} | ABC | + | Should Be Equal | ${str2} | 1A2C3D | + """ + return string.upper() + + def convert_to_title_case(self, string, exclude=None): + """Converts string to title case. + + Uses the following algorithm: + + - Split the string to words from whitespace characters (spaces, + newlines, etc.). + - Exclude words that are not all lower case. This preserves, + for example, "OK" and "iPhone". + - Exclude also words listed in the optional ``exclude`` argument. + - Title case the first alphabetical character of each word that has + not been excluded. + - Join all words together so that original whitespace is preserved. + + Explicitly excluded words can be given as a list or as a string with + words separated by a comma and an optional space. Excluded words are + actually considered to be regular expression patterns, so it is + possible to use something like "example[.!?]?" to match the word + "example" on it own and also if followed by ".", "!" or "?". + See `BuiltIn.Should Match Regexp` for more information about Python + regular expression syntax in general and how to use it in Robot + Framework test data in particular. + + Examples: + | ${str1} = | Convert To Title Case | hello, world! | + | ${str2} = | Convert To Title Case | it's an OK iPhone | exclude=a, an, the | + | ${str3} = | Convert To Title Case | distance is 1 km. | exclude=is, km.? | + | Should Be Equal | ${str1} | Hello, World! | + | Should Be Equal | ${str2} | It's an OK iPhone | + | Should Be Equal | ${str3} | Distance is 1 km. | + + The reason this keyword does not use Python's standard + [https://docs.python.org/library/stdtypes.html#str.title|title()] + method is that it can yield undesired results, for example, if + strings contain upper case letters or special characters like + apostrophes. It would, for example, convert "it's an OK iPhone" + to "It'S An Ok Iphone". + + New in Robot Framework 3.2. + """ + if is_string(exclude): + exclude = [e.strip() for e in exclude.split(',')] + elif not exclude: + exclude = [] + exclude = [re.compile('^%s$' % e) for e in exclude] + + def title(word): + if any(e.match(word) for e in exclude) or not word.islower(): + return word + for index, char in enumerate(word): + if char.isalpha(): + return word[:index] + word[index].title() + word[index+1:] + return word + + tokens = re.split(r'(\s+)', string, flags=re.UNICODE) + return ''.join(title(token) for token in tokens) + + def encode_string_to_bytes(self, string, encoding, errors='strict'): + """Encodes the given Unicode ``string`` to bytes using the given ``encoding``. + + ``errors`` argument controls what to do if encoding some characters fails. + All values accepted by ``encode`` method in Python are valid, but in + practice the following values are most useful: + + - ``strict``: fail if characters cannot be encoded (default) + - ``ignore``: ignore characters that cannot be encoded + - ``replace``: replace characters that cannot be encoded with + a replacement character + + Examples: + | ${bytes} = | Encode String To Bytes | ${string} | UTF-8 | + | ${bytes} = | Encode String To Bytes | ${string} | ASCII | errors=ignore | + + Use `Convert To Bytes` in ``BuiltIn`` if you want to create bytes based + on character or integer sequences. Use `Decode Bytes To String` if you + need to convert byte strings to Unicode strings and `Convert To String` + in ``BuiltIn`` if you need to convert arbitrary objects to Unicode. + """ + return bytes(string.encode(encoding, errors)) + + def decode_bytes_to_string(self, bytes, encoding, errors='strict'): + """Decodes the given ``bytes`` to a Unicode string using the given ``encoding``. + + ``errors`` argument controls what to do if decoding some bytes fails. + All values accepted by ``decode`` method in Python are valid, but in + practice the following values are most useful: + + - ``strict``: fail if characters cannot be decoded (default) + - ``ignore``: ignore characters that cannot be decoded + - ``replace``: replace characters that cannot be decoded with + a replacement character + + Examples: + | ${string} = | Decode Bytes To String | ${bytes} | UTF-8 | + | ${string} = | Decode Bytes To String | ${bytes} | ASCII | errors=ignore | + + Use `Encode String To Bytes` if you need to convert Unicode strings to + byte strings, and `Convert To String` in ``BuiltIn`` if you need to + convert arbitrary objects to Unicode strings. + """ + if PY3 and is_unicode(bytes): + raise TypeError('Can not decode strings on Python 3.') + return bytes.decode(encoding, errors) + + def format_string(self, template, *positional, **named): + """Formats a ``template`` using the given ``positional`` and ``named`` arguments. + + The template can be either be a string or an absolute path to + an existing file. In the latter case the file is read and its contents + are used as the template. If the template file contains non-ASCII + characters, it must be encoded using UTF-8. + + The template is formatted using Python's + [https://docs.python.org/library/string.html#format-string-syntax|format + string syntax]. Placeholders are marked using ``{}`` with possible + field name and format specification inside. Literal curly braces + can be inserted by doubling them like `{{` and `}}`. + + Examples: + | ${to} = | Format String | To: {} <{}> | ${user} | ${email} | + | ${to} = | Format String | To: {name} <{email}> | name=${name} | email=${email} | + | ${to} = | Format String | To: {user.name} <{user.email}> | user=${user} | + | ${xx} = | Format String | {:*^30} | centered | + | ${yy} = | Format String | {0:{width}{base}} | ${42} | base=X | width=10 | + | ${zz} = | Format String | ${CURDIR}/template.txt | positional | named=value | + + New in Robot Framework 3.1. + """ + if os.path.isabs(template) and os.path.isfile(template): + template = template.replace('/', os.sep) + logger.info('Reading template from file %s.' + % (template, template), html=True) + with FileReader(template) as reader: + template = reader.read() + return template.format(*positional, **named) + + def get_line_count(self, string): + """Returns and logs the number of lines in the given string.""" + count = len(string.splitlines()) + logger.info('%d lines' % count) + return count + + def split_to_lines(self, string, start=0, end=None): + """Splits the given string to lines. + + It is possible to get only a selection of lines from ``start`` + to ``end`` so that ``start`` index is inclusive and ``end`` is + exclusive. Line numbering starts from 0, and it is possible to + use negative indices to refer to lines from the end. + + Lines are returned without the newlines. The number of + returned lines is automatically logged. + + Examples: + | @{lines} = | Split To Lines | ${manylines} | | | + | @{ignore first} = | Split To Lines | ${manylines} | 1 | | + | @{ignore last} = | Split To Lines | ${manylines} | | -1 | + | @{5th to 10th} = | Split To Lines | ${manylines} | 4 | 10 | + | @{first two} = | Split To Lines | ${manylines} | | 1 | + | @{last two} = | Split To Lines | ${manylines} | -2 | | + + Use `Get Line` if you only need to get a single line. + """ + start = self._convert_to_index(start, 'start') + end = self._convert_to_index(end, 'end') + lines = string.splitlines()[start:end] + logger.info('%d lines returned' % len(lines)) + return lines + + def get_line(self, string, line_number): + """Returns the specified line from the given ``string``. + + Line numbering starts from 0 and it is possible to use + negative indices to refer to lines from the end. The line is + returned without the newline character. + + Examples: + | ${first} = | Get Line | ${string} | 0 | + | ${2nd last} = | Get Line | ${string} | -2 | + + Use `Split To Lines` if all lines are needed. + """ + line_number = self._convert_to_integer(line_number, 'line_number') + return string.splitlines()[line_number] + + def get_lines_containing_string(self, string, pattern, case_insensitive=False): + """Returns lines of the given ``string`` that contain the ``pattern``. + + The ``pattern`` is always considered to be a normal string, not a glob + or regexp pattern. A line matches if the ``pattern`` is found anywhere + on it. + + The match is case-sensitive by default, but giving ``case_insensitive`` + a true value makes it case-insensitive. The value is considered true + if it is a non-empty string that is not equal to ``false``, ``none`` or + ``no``. If the value is not a string, its truth value is got directly + in Python. Considering ``none`` false is new in RF 3.0.3. + + Lines are returned as one string catenated back together with + newlines. Possible trailing newline is never returned. The + number of matching lines is automatically logged. + + Examples: + | ${lines} = | Get Lines Containing String | ${result} | An example | + | ${ret} = | Get Lines Containing String | ${ret} | FAIL | case-insensitive | + + See `Get Lines Matching Pattern` and `Get Lines Matching Regexp` + if you need more complex pattern matching. + """ + if is_truthy(case_insensitive): + pattern = pattern.lower() + contains = lambda line: pattern in line.lower() + else: + contains = lambda line: pattern in line + return self._get_matching_lines(string, contains) + + def get_lines_matching_pattern(self, string, pattern, case_insensitive=False): + """Returns lines of the given ``string`` that match the ``pattern``. + + The ``pattern`` is a _glob pattern_ where: + | ``*`` | matches everything | + | ``?`` | matches any single character | + | ``[chars]`` | matches any character inside square brackets (e.g. ``[abc]`` matches either ``a``, ``b`` or ``c``) | + | ``[!chars]`` | matches any character not inside square brackets | + + A line matches only if it matches the ``pattern`` fully. + + The match is case-sensitive by default, but giving ``case_insensitive`` + a true value makes it case-insensitive. The value is considered true + if it is a non-empty string that is not equal to ``false``, ``none`` or + ``no``. If the value is not a string, its truth value is got directly + in Python. Considering ``none`` false is new in RF 3.0.3. + + Lines are returned as one string catenated back together with + newlines. Possible trailing newline is never returned. The + number of matching lines is automatically logged. + + Examples: + | ${lines} = | Get Lines Matching Pattern | ${result} | Wild???? example | + | ${ret} = | Get Lines Matching Pattern | ${ret} | FAIL: * | case_insensitive=true | + + See `Get Lines Matching Regexp` if you need more complex + patterns and `Get Lines Containing String` if searching + literal strings is enough. + """ + if is_truthy(case_insensitive): + pattern = pattern.lower() + matches = lambda line: fnmatchcase(line.lower(), pattern) + else: + matches = lambda line: fnmatchcase(line, pattern) + return self._get_matching_lines(string, matches) + + def get_lines_matching_regexp(self, string, pattern, partial_match=False): + """Returns lines of the given ``string`` that match the regexp ``pattern``. + + See `BuiltIn.Should Match Regexp` for more information about + Python regular expression syntax in general and how to use it + in Robot Framework test data in particular. + + By default lines match only if they match the pattern fully, but + partial matching can be enabled by giving the ``partial_match`` + argument a true value. The value is considered true + if it is a non-empty string that is not equal to ``false``, ``none`` or + ``no``. If the value is not a string, its truth value is got directly + in Python. Considering ``none`` false is new in RF 3.0.3. + + If the pattern is empty, it matches only empty lines by default. + When partial matching is enabled, empty pattern matches all lines. + + Notice that to make the match case-insensitive, you need to prefix + the pattern with case-insensitive flag ``(?i)``. + + Lines are returned as one string concatenated back together with + newlines. Possible trailing newline is never returned. The + number of matching lines is automatically logged. + + Examples: + | ${lines} = | Get Lines Matching Regexp | ${result} | Reg\\\\w{3} example | + | ${lines} = | Get Lines Matching Regexp | ${result} | Reg\\\\w{3} example | partial_match=true | + | ${ret} = | Get Lines Matching Regexp | ${ret} | (?i)FAIL: .* | + + See `Get Lines Matching Pattern` and `Get Lines Containing + String` if you do not need full regular expression powers (and + complexity). + + ``partial_match`` argument is new in Robot Framework 2.9. In earlier + versions exact match was always required. + """ + if not is_truthy(partial_match): + pattern = '^%s$' % pattern + return self._get_matching_lines(string, re.compile(pattern).search) + + def _get_matching_lines(self, string, matches): + lines = string.splitlines() + matching = [line for line in lines if matches(line)] + logger.info('%d out of %d lines matched' % (len(matching), len(lines))) + return '\n'.join(matching) + + def get_regexp_matches(self, string, pattern, *groups): + """Returns a list of all non-overlapping matches in the given string. + + ``string`` is the string to find matches from and ``pattern`` is the + regular expression. See `BuiltIn.Should Match Regexp` for more + information about Python regular expression syntax in general and how + to use it in Robot Framework test data in particular. + + If no groups are used, the returned list contains full matches. If one + group is used, the list contains only contents of that group. If + multiple groups are used, the list contains tuples that contain + individual group contents. All groups can be given as indexes (starting + from 1) and named groups also as names. + + Examples: + | ${no match} = | Get Regexp Matches | the string | xxx | + | ${matches} = | Get Regexp Matches | the string | t.. | + | ${one group} = | Get Regexp Matches | the string | t(..) | 1 | + | ${named group} = | Get Regexp Matches | the string | t(?P..) | name | + | ${two groups} = | Get Regexp Matches | the string | t(.)(.) | 1 | 2 | + => + | ${no match} = [] + | ${matches} = ['the', 'tri'] + | ${one group} = ['he', 'ri'] + | ${named group} = ['he', 'ri'] + | ${two groups} = [('h', 'e'), ('r', 'i')] + + New in Robot Framework 2.9. + """ + regexp = re.compile(pattern) + groups = [self._parse_group(g) for g in groups] + return [m.group(*groups) for m in regexp.finditer(string)] + + def _parse_group(self, group): + try: + return int(group) + except ValueError: + return group + + def replace_string(self, string, search_for, replace_with, count=-1): + """Replaces ``search_for`` in the given ``string`` with ``replace_with``. + + ``search_for`` is used as a literal string. See `Replace String + Using Regexp` if more powerful pattern matching is needed. + If you need to just remove a string see `Remove String`. + + If the optional argument ``count`` is given, only that many + occurrences from left are replaced. Negative ``count`` means + that all occurrences are replaced (default behaviour) and zero + means that nothing is done. + + A modified version of the string is returned and the original + string is not altered. + + Examples: + | ${str} = | Replace String | Hello, world! | world | tellus | + | Should Be Equal | ${str} | Hello, tellus! | | | + | ${str} = | Replace String | Hello, world! | l | ${EMPTY} | count=1 | + | Should Be Equal | ${str} | Helo, world! | | | + """ + count = self._convert_to_integer(count, 'count') + return string.replace(search_for, replace_with, count) + + def replace_string_using_regexp(self, string, pattern, replace_with, count=-1): + """Replaces ``pattern`` in the given ``string`` with ``replace_with``. + + This keyword is otherwise identical to `Replace String`, but + the ``pattern`` to search for is considered to be a regular + expression. See `BuiltIn.Should Match Regexp` for more + information about Python regular expression syntax in general + and how to use it in Robot Framework test data in particular. + + If you need to just remove a string see `Remove String Using Regexp`. + + Examples: + | ${str} = | Replace String Using Regexp | ${str} | 20\\\\d\\\\d-\\\\d\\\\d-\\\\d\\\\d | | + | ${str} = | Replace String Using Regexp | ${str} | (Hello|Hi) | ${EMPTY} | count=1 | + """ + count = self._convert_to_integer(count, 'count') + # re.sub handles 0 and negative counts differently than string.replace + if count == 0: + return string + return re.sub(pattern, replace_with, string, max(count, 0)) + + def remove_string(self, string, *removables): + """Removes all ``removables`` from the given ``string``. + + ``removables`` are used as literal strings. Each removable will be + matched to a temporary string from which preceding removables have + been already removed. See second example below. + + Use `Remove String Using Regexp` if more powerful pattern matching is + needed. If only a certain number of matches should be removed, + `Replace String` or `Replace String Using Regexp` can be used. + + A modified version of the string is returned and the original + string is not altered. + + Examples: + | ${str} = | Remove String | Robot Framework | work | + | Should Be Equal | ${str} | Robot Frame | + | ${str} = | Remove String | Robot Framework | o | bt | + | Should Be Equal | ${str} | R Framewrk | + """ + for removable in removables: + string = self.replace_string(string, removable, '') + return string + + def remove_string_using_regexp(self, string, *patterns): + """Removes ``patterns`` from the given ``string``. + + This keyword is otherwise identical to `Remove String`, but + the ``patterns`` to search for are considered to be a regular + expression. See `Replace String Using Regexp` for more information + about the regular expression syntax. That keyword can also be + used if there is a need to remove only a certain number of + occurrences. + """ + for pattern in patterns: + string = self.replace_string_using_regexp(string, pattern, '') + return string + + def split_string(self, string, separator=None, max_split=-1): + """Splits the ``string`` using ``separator`` as a delimiter string. + + If a ``separator`` is not given, any whitespace string is a + separator. In that case also possible consecutive whitespace + as well as leading and trailing whitespace is ignored. + + Split words are returned as a list. If the optional + ``max_split`` is given, at most ``max_split`` splits are done, and + the returned list will have maximum ``max_split + 1`` elements. + + Examples: + | @{words} = | Split String | ${string} | + | @{words} = | Split String | ${string} | ,${SPACE} | + | ${pre} | ${post} = | Split String | ${string} | :: | 1 | + + See `Split String From Right` if you want to start splitting + from right, and `Fetch From Left` and `Fetch From Right` if + you only want to get first/last part of the string. + """ + if separator == '': + separator = None + max_split = self._convert_to_integer(max_split, 'max_split') + return string.split(separator, max_split) + + def split_string_from_right(self, string, separator=None, max_split=-1): + """Splits the ``string`` using ``separator`` starting from right. + + Same as `Split String`, but splitting is started from right. This has + an effect only when ``max_split`` is given. + + Examples: + | ${first} | ${rest} = | Split String | ${string} | - | 1 | + | ${rest} | ${last} = | Split String From Right | ${string} | - | 1 | + """ + if separator == '': + separator = None + max_split = self._convert_to_integer(max_split, 'max_split') + return string.rsplit(separator, max_split) + + def split_string_to_characters(self, string): + """Splits the given ``string`` to characters. + + Example: + | @{characters} = | Split String To Characters | ${string} | + """ + return list(string) + + def fetch_from_left(self, string, marker): + """Returns contents of the ``string`` before the first occurrence of ``marker``. + + If the ``marker`` is not found, whole string is returned. + + See also `Fetch From Right`, `Split String` and `Split String + From Right`. + """ + return string.split(marker)[0] + + def fetch_from_right(self, string, marker): + """Returns contents of the ``string`` after the last occurrence of ``marker``. + + If the ``marker`` is not found, whole string is returned. + + See also `Fetch From Left`, `Split String` and `Split String + From Right`. + """ + return string.split(marker)[-1] + + def generate_random_string(self, length=8, chars='[LETTERS][NUMBERS]'): + """Generates a string with a desired ``length`` from the given ``chars``. + + The population sequence ``chars`` contains the characters to use + when generating the random string. It can contain any + characters, and it is possible to use special markers + explained in the table below: + + | = Marker = | = Explanation = | + | ``[LOWER]`` | Lowercase ASCII characters from ``a`` to ``z``. | + | ``[UPPER]`` | Uppercase ASCII characters from ``A`` to ``Z``. | + | ``[LETTERS]`` | Lowercase and uppercase ASCII characters. | + | ``[NUMBERS]`` | Numbers from 0 to 9. | + + Examples: + | ${ret} = | Generate Random String | + | ${low} = | Generate Random String | 12 | [LOWER] | + | ${bin} = | Generate Random String | 8 | 01 | + | ${hex} = | Generate Random String | 4 | [NUMBERS]abcdef | + """ + if length == '': + length = 8 + length = self._convert_to_integer(length, 'length') + for name, value in [('[LOWER]', ascii_lowercase), + ('[UPPER]', ascii_uppercase), + ('[LETTERS]', ascii_lowercase + ascii_uppercase), + ('[NUMBERS]', digits)]: + chars = chars.replace(name, value) + maxi = len(chars) - 1 + return ''.join(chars[randint(0, maxi)] for _ in range(length)) + + def get_substring(self, string, start, end=None): + """Returns a substring from ``start`` index to ``end`` index. + + The ``start`` index is inclusive and ``end`` is exclusive. + Indexing starts from 0, and it is possible to use + negative indices to refer to characters from the end. + + Examples: + | ${ignore first} = | Get Substring | ${string} | 1 | | + | ${ignore last} = | Get Substring | ${string} | | -1 | + | ${5th to 10th} = | Get Substring | ${string} | 4 | 10 | + | ${first two} = | Get Substring | ${string} | | 1 | + | ${last two} = | Get Substring | ${string} | -2 | | + """ + start = self._convert_to_index(start, 'start') + end = self._convert_to_index(end, 'end') + return string[start:end] + + def strip_string(self, string, mode='both', characters=None): + """Remove leading and/or trailing whitespaces from the given string. + + ``mode`` is either ``left`` to remove leading characters, ``right`` to + remove trailing characters, ``both`` (default) to remove the + characters from both sides of the string or ``none`` to return the + unmodified string. + + If the optional ``characters`` is given, it must be a string and the + characters in the string will be stripped in the string. Please note, + that this is not a substring to be removed but a list of characters, + see the example below. + + Examples: + | ${stripped}= | Strip String | ${SPACE}Hello${SPACE} | | + | Should Be Equal | ${stripped} | Hello | | + | ${stripped}= | Strip String | ${SPACE}Hello${SPACE} | mode=left | + | Should Be Equal | ${stripped} | Hello${SPACE} | | + | ${stripped}= | Strip String | aabaHelloeee | characters=abe | + | Should Be Equal | ${stripped} | Hello | | + + New in Robot Framework 3.0. + """ + try: + method = {'BOTH': string.strip, + 'LEFT': string.lstrip, + 'RIGHT': string.rstrip, + 'NONE': lambda characters: string}[mode.upper()] + except KeyError: + raise ValueError("Invalid mode '%s'." % mode) + return method(characters) + + def should_be_string(self, item, msg=None): + """Fails if the given ``item`` is not a string. + + With Python 2, except with IronPython, this keyword passes regardless + is the ``item`` a Unicode string or a byte string. Use `Should Be + Unicode String` or `Should Be Byte String` if you want to restrict + the string type. Notice that with Python 2, except with IronPython, + ``'string'`` creates a byte string and ``u'unicode'`` must be used to + create a Unicode string. + + With Python 3 and IronPython, this keyword passes if the string is + a Unicode string but fails if it is bytes. Notice that with both + Python 3 and IronPython, ``'string'`` creates a Unicode string, and + ``b'bytes'`` must be used to create a byte string. + + The default error message can be overridden with the optional + ``msg`` argument. + """ + if not is_string(item): + self._fail(msg, "'%s' is not a string.", item) + + def should_not_be_string(self, item, msg=None): + """Fails if the given ``item`` is a string. + + See `Should Be String` for more details about Unicode strings and byte + strings. + + The default error message can be overridden with the optional + ``msg`` argument. + """ + if is_string(item): + self._fail(msg, "'%s' is a string.", item) + + def should_be_unicode_string(self, item, msg=None): + """Fails if the given ``item`` is not a Unicode string. + + Use `Should Be Byte String` if you want to verify the ``item`` is a + byte string, or `Should Be String` if both Unicode and byte strings + are fine. See `Should Be String` for more details about Unicode + strings and byte strings. + + The default error message can be overridden with the optional + ``msg`` argument. + """ + if not is_unicode(item): + self._fail(msg, "'%s' is not a Unicode string.", item) + + def should_be_byte_string(self, item, msg=None): + """Fails if the given ``item`` is not a byte string. + + Use `Should Be Unicode String` if you want to verify the ``item`` is a + Unicode string, or `Should Be String` if both Unicode and byte strings + are fine. See `Should Be String` for more details about Unicode strings + and byte strings. + + The default error message can be overridden with the optional + ``msg`` argument. + """ + if not is_bytes(item): + self._fail(msg, "'%s' is not a byte string.", item) + + def should_be_lowercase(self, string, msg=None): + """Fails if the given ``string`` is not in lowercase. + + For example, ``'string'`` and ``'with specials!'`` would pass, and + ``'String'``, ``''`` and ``' '`` would fail. + + The default error message can be overridden with the optional + ``msg`` argument. + + See also `Should Be Uppercase` and `Should Be Titlecase`. + """ + if not string.islower(): + self._fail(msg, "'%s' is not lowercase.", string) + + def should_be_uppercase(self, string, msg=None): + """Fails if the given ``string`` is not in uppercase. + + For example, ``'STRING'`` and ``'WITH SPECIALS!'`` would pass, and + ``'String'``, ``''`` and ``' '`` would fail. + + The default error message can be overridden with the optional + ``msg`` argument. + + See also `Should Be Titlecase` and `Should Be Lowercase`. + """ + if not string.isupper(): + self._fail(msg, "'%s' is not uppercase.", string) + + def should_be_titlecase(self, string, msg=None): + """Fails if given ``string`` is not title. + + ``string`` is a titlecased string if there is at least one + character in it, uppercase characters only follow uncased + characters and lowercase characters only cased ones. + + For example, ``'This Is Title'`` would pass, and ``'Word In UPPER'``, + ``'Word In lower'``, ``''`` and ``' '`` would fail. + + The default error message can be overridden with the optional + ``msg`` argument. + + See also `Should Be Uppercase` and `Should Be Lowercase`. + """ + if not string.istitle(): + self._fail(msg, "'%s' is not titlecase.", string) + + def _convert_to_index(self, value, name): + if value == '': + return 0 + if value is None: + return None + return self._convert_to_integer(value, name) + + def _convert_to_integer(self, value, name): + try: + return int(value) + except ValueError: + raise ValueError("Cannot convert '%s' argument '%s' to an integer." + % (name, value)) + + def _fail(self, message, default_template, *items): + if not message: + message = default_template % tuple(unic(item) for item in items) + raise AssertionError(message) diff --git a/robot/lib/python3.8/site-packages/robot/libraries/Telnet.py b/robot/lib/python3.8/site-packages/robot/libraries/Telnet.py new file mode 100644 index 0000000000000000000000000000000000000000..3d5d6151bf902045a65a9e1759896b81914a0b98 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/libraries/Telnet.py @@ -0,0 +1,1244 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from contextlib import contextmanager +import inspect +import re +import socket +import struct +import telnetlib +import time + +try: + import pyte +except ImportError: + pyte = None + +from robot.api import logger +from robot.api.deco import keyword +from robot.utils import (ConnectionCache, is_bytes, is_string, is_truthy, + is_unicode, secs_to_timestr, seq2str, timestr_to_secs) +from robot.version import get_version + + +class Telnet(object): + """A test library providing communication over Telnet connections. + + ``Telnet`` is Robot Framework's standard library that makes it possible to + connect to Telnet servers and execute commands on the opened connections. + + == Table of contents == + + %TOC% + + = Connections = + + The first step of using ``Telnet`` is opening a connection with `Open + Connection` keyword. Typically the next step is logging in with `Login` + keyword, and in the end the opened connection can be closed with `Close + Connection`. + + It is possible to open multiple connections and switch the active one + using `Switch Connection`. `Close All Connections` can be used to close + all the connections, which is especially useful in suite teardowns to + guarantee that all connections are always closed. + + = Writing and reading = + + After opening a connection and possibly logging in, commands can be + executed or text written to the connection for other reasons using `Write` + and `Write Bare` keywords. The main difference between these two is that + the former adds a [#Configuration|configurable newline] after the text + automatically. + + After writing something to the connection, the resulting output can be + read using `Read`, `Read Until`, `Read Until Regexp`, and `Read Until + Prompt` keywords. Which one to use depends on the context, but the latest + one is often the most convenient. + + As a convenience when running a command, it is possible to use `Execute + Command` that simply uses `Write` and `Read Until Prompt` internally. + `Write Until Expected Output` is useful if you need to wait until writing + something produces a desired output. + + Written and read text is automatically encoded/decoded using a + [#Configuration|configured encoding]. + + The ANSI escape codes, like cursor movement and color codes, are + normally returned as part of the read operation. If an escape code occurs + in middle of a search pattern it may also prevent finding the searched + string. `Terminal emulation` can be used to process these + escape codes as they would be if a real terminal would be in use. + + = Configuration = + + Many aspects related the connections can be easily configured either + globally or per connection basis. Global configuration is done when + [#Importing|library is imported], and these values can be overridden per + connection by `Open Connection` or with setting specific keywords + `Set Timeout`, `Set Newline`, `Set Prompt`, `Set Encoding`, + `Set Default Log Level` and `Set Telnetlib Log Level`. + + Values of ``environ_user``, ``window_size``, ``terminal_emulation``, and + ``terminal_type`` can not be changed after opening the connection. + + == Timeout == + + Timeout defines how long is the maximum time to wait when reading + output. It is used internally by `Read Until`, `Read Until Regexp`, + `Read Until Prompt`, and `Login` keywords. The default value is 3 seconds. + + == Connection Timeout == + + Connection Timeout defines how long is the maximum time to wait when + opening the telnet connection. It is used internally by `Open Connection`. + The default value is the system global default timeout. + + New in Robot Framework 2.9.2. + + == Newline == + + Newline defines which line separator `Write` keyword should use. The + default value is ``CRLF`` that is typically used by Telnet connections. + + Newline can be given either in escaped format using ``\\n`` and ``\\r`` or + with special ``LF`` and ``CR`` syntax. + + Examples: + | `Set Newline` | \\n | + | `Set Newline` | CRLF | + + == Prompt == + + Often the easiest way to read the output of a command is reading all + the output until the next prompt with `Read Until Prompt`. It also makes + it easier, and faster, to verify did `Login` succeed. + + Prompt can be specified either as a normal string or a regular expression. + The latter is especially useful if the prompt changes as a result of + the executed commands. Prompt can be set to be a regular expression + by giving ``prompt_is_regexp`` argument a true value (see `Boolean + arguments`). + + Examples: + | `Open Connection` | lolcathost | prompt=$ | + | `Set Prompt` | (> |# ) | prompt_is_regexp=true | + + == Encoding == + + To ease handling text containing non-ASCII characters, all written text is + encoded and read text decoded by default. The default encoding is UTF-8 + that works also with ASCII. Encoding can be disabled by using a special + encoding value ``NONE``. This is mainly useful if you need to get the bytes + received from the connection as-is. + + Notice that when writing to the connection, only Unicode strings are + encoded using the defined encoding. Byte strings are expected to be already + encoded correctly. Notice also that normal text in test data is passed to + the library as Unicode and you need to use variables to use bytes. + + It is also possible to configure the error handler to use if encoding or + decoding characters fails. Accepted values are the same that encode/decode + functions in Python strings accept. In practice the following values are + the most useful: + + - ``ignore``: ignore characters that cannot be encoded (default) + - ``strict``: fail if characters cannot be encoded + - ``replace``: replace characters that cannot be encoded with a replacement + character + + Examples: + | `Open Connection` | lolcathost | encoding=Latin1 | encoding_errors=strict | + | `Set Encoding` | ISO-8859-15 | + | `Set Encoding` | errors=ignore | + + == Default log level == + + Default log level specifies the log level keywords use for `logging` unless + they are given an explicit log level. The default value is ``INFO``, and + changing it, for example, to ``DEBUG`` can be a good idea if there is lot + of unnecessary output that makes log files big. + + == Terminal type == + + By default the Telnet library does not negotiate any specific terminal type + with the server. If a specific terminal type, for example ``vt100``, is + desired, the terminal type can be configured in `importing` and with + `Open Connection`. + + == Window size == + + Window size for negotiation with the server can be configured when + `importing` the library and with `Open Connection`. + + == USER environment variable == + + Telnet protocol allows the ``USER`` environment variable to be sent when + connecting to the server. On some servers it may happen that there is no + login prompt, and on those cases this configuration option will allow still + to define the desired username. The option ``environ_user`` can be used in + `importing` and with `Open Connection`. + + = Terminal emulation = + + Telnet library supports terminal + emulation with [http://pyte.readthedocs.io|Pyte]. Terminal emulation + will process the output in a virtual screen. This means that ANSI escape + codes, like cursor movements, and also control characters, like + carriage returns and backspaces, have the same effect on the result as they + would have on a normal terminal screen. For example the sequence + ``acdc\\x1b[3Dbba`` will result in output ``abba``. + + Terminal emulation is taken into use by giving ``terminal_emulation`` + argument a true value (see `Boolean arguments`) either in the library + initialization or with `Open Connection`. + + As Pyte approximates vt-style terminal, you may also want to set the + terminal type as ``vt100``. We also recommend that you increase the window + size, as the terminal emulation will break all lines that are longer than + the window row length. + + When terminal emulation is used, the `newline` and `encoding` can not be + changed anymore after opening the connection. + + Examples: + | `Open Connection` | lolcathost | terminal_emulation=True | terminal_type=vt100 | window_size=400x100 | + + As a prerequisite for using terminal emulation, you need to have Pyte + installed. Due to backwards incompatible changes in Pyte, different + Robot Framework versions support different Pyte versions: + + - Pyte 0.6 and newer are supported by Robot Framework 3.0.3. + Latest Pyte version can be installed (or upgraded) with + ``pip install --upgrade pyte``. + - Pyte 0.5.2 and older are supported by Robot Framework 3.0.2 and earlier. + Pyte 0.5.2 can be installed with ``pip install pyte==0.5.2``. + + = Logging = + + All keywords that read something log the output. These keywords take the + log level to use as an optional argument, and if no log level is specified + they use the [#Configuration|configured] default value. + + The valid log levels to use are ``TRACE``, ``DEBUG``, ``INFO`` (default), + and ``WARN``. Levels below ``INFO`` are not shown in log files by default + whereas warnings are shown more prominently. + + The [http://docs.python.org/library/telnetlib.html|telnetlib module] + used by this library has a custom logging system for logging content it + sends and receives. By default these messages are written using ``TRACE`` + level, but the level is configurable with the ``telnetlib_log_level`` + option either in the library initialization, to the `Open Connection` + or by using the `Set Telnetlib Log Level` keyword to the active + connection. Special level ``NONE`` con be used to disable the logging + altogether. + + = Time string format = + + Timeouts and other times used must be given as a time string using format + like ``15 seconds`` or ``1min 10s``. If the timeout is given as just + a number, for example, ``10`` or ``1.5``, it is considered to be seconds. + The time string format is described in more detail in an appendix of + [http://robotframework.org/robotframework/#user-guide|Robot Framework User Guide]. + + = Boolean arguments = + + Some keywords accept arguments that are handled as Boolean values true or + false. If such an argument is given as a string, it is considered false if + it is an empty string or equal to ``FALSE``, ``NONE``, ``NO``, ``OFF`` or + ``0``, case-insensitively. Other strings are considered true regardless + their value, and other argument types are tested using the same + [http://docs.python.org/library/stdtypes.html#truth|rules as in Python]. + + True examples: + | `Open Connection` | lolcathost | terminal_emulation=True | # Strings are generally true. | + | `Open Connection` | lolcathost | terminal_emulation=yes | # Same as the above. | + | `Open Connection` | lolcathost | terminal_emulation=${TRUE} | # Python ``True`` is true. | + | `Open Connection` | lolcathost | terminal_emulation=${42} | # Numbers other than 0 are true. | + + False examples: + | `Open Connection` | lolcathost | terminal_emulation=False | # String ``false`` is false. | + | `Open Connection` | lolcathost | terminal_emulation=no | # Also string ``no`` is false. | + | `Open Connection` | lolcathost | terminal_emulation=${EMPTY} | # Empty string is false. | + | `Open Connection` | lolcathost | terminal_emulation=${FALSE} | # Python ``False`` is false. | + + Considering string ``NONE`` false is new in Robot Framework 3.0.3 and + considering also ``OFF`` and ``0`` false is new in Robot Framework 3.1. + """ + ROBOT_LIBRARY_SCOPE = 'TEST_SUITE' + ROBOT_LIBRARY_VERSION = get_version() + + def __init__(self, timeout='3 seconds', newline='CRLF', + prompt=None, prompt_is_regexp=False, + encoding='UTF-8', encoding_errors='ignore', + default_log_level='INFO', window_size=None, + environ_user=None, terminal_emulation=False, + terminal_type=None, telnetlib_log_level='TRACE', + connection_timeout=None): + """Telnet library can be imported with optional configuration parameters. + + Configuration parameters are used as default values when new + connections are opened with `Open Connection` keyword. They can also be + overridden after opening the connection using the `Set ...` `keywords`. + See these keywords as well as `Configuration`, `Terminal emulation` and + `Logging` sections above for more information about these parameters + and their possible values. + + See `Time string format` and `Boolean arguments` sections for + information about using arguments accepting times and Boolean values, + respectively. + + Examples (use only one of these): + | = Setting = | = Value = | = Value = | = Value = | = Value = | = Comment = | + | Library | Telnet | | | | # default values | + | Library | Telnet | 5 seconds | | | # set only timeout | + | Library | Telnet | newline=LF | encoding=ISO-8859-1 | | # set newline and encoding using named arguments | + | Library | Telnet | prompt=$ | | | # set prompt | + | Library | Telnet | prompt=(> |# ) | prompt_is_regexp=yes | | # set prompt as a regular expression | + | Library | Telnet | terminal_emulation=True | terminal_type=vt100 | window_size=400x100 | # use terminal emulation with defined window size and terminal type | + | Library | Telnet | telnetlib_log_level=NONE | | | # disable logging messages from the underlying telnetlib | + """ + self._timeout = timeout or 3.0 + self._set_connection_timeout(connection_timeout) + self._newline = newline or 'CRLF' + self._prompt = (prompt, prompt_is_regexp) + self._encoding = encoding + self._encoding_errors = encoding_errors + self._default_log_level = default_log_level + self._window_size = window_size + self._environ_user = environ_user + self._terminal_emulation = terminal_emulation + self._terminal_type = terminal_type + self._telnetlib_log_level = telnetlib_log_level + self._cache = ConnectionCache() + self._conn = None + self._conn_kws = self._lib_kws = None + + def get_keyword_names(self): + return self._get_library_keywords() + self._get_connection_keywords() + + def _get_library_keywords(self): + if self._lib_kws is None: + self._lib_kws = self._get_keywords(self, ['get_keyword_names']) + return self._lib_kws + + def _get_keywords(self, source, excluded): + return [name for name in dir(source) + if self._is_keyword(name, source, excluded)] + + def _is_keyword(self, name, source, excluded): + return (name not in excluded and + not name.startswith('_') and + name != 'get_keyword_names' and + inspect.ismethod(getattr(source, name))) + + def _get_connection_keywords(self): + if self._conn_kws is None: + conn = self._get_connection() + excluded = [name for name in dir(telnetlib.Telnet()) + if name not in ['write', 'read', 'read_until']] + self._conn_kws = self._get_keywords(conn, excluded) + return self._conn_kws + + def __getattr__(self, name): + if name not in self._get_connection_keywords(): + raise AttributeError(name) + # If no connection is initialized, get attributes from a non-active + # connection. This makes it possible for Robot to create keyword + # handlers when it imports the library. + return getattr(self._conn or self._get_connection(), name) + + @keyword(types=None) + def open_connection(self, host, alias=None, port=23, timeout=None, + newline=None, prompt=None, prompt_is_regexp=False, + encoding=None, encoding_errors=None, + default_log_level=None, window_size=None, + environ_user=None, terminal_emulation=None, + terminal_type=None, telnetlib_log_level=None, + connection_timeout=None): + """Opens a new Telnet connection to the given host and port. + + The ``timeout``, ``newline``, ``prompt``, ``prompt_is_regexp``, + ``encoding``, ``default_log_level``, ``window_size``, ``environ_user``, + ``terminal_emulation``, ``terminal_type`` and ``telnetlib_log_level`` + arguments get default values when the library is [#Importing|imported]. + Setting them here overrides those values for the opened connection. + See `Configuration`, `Terminal emulation` and `Logging` sections for + more information about these parameters and their possible values. + + Possible already opened connections are cached and it is possible to + switch back to them using `Switch Connection` keyword. It is possible to + switch either using explicitly given ``alias`` or using index returned + by this keyword. Indexing starts from 1 and is reset back to it by + `Close All Connections` keyword. + """ + timeout = timeout or self._timeout + connection_timeout = (timestr_to_secs(connection_timeout) + if connection_timeout + else self._connection_timeout) + newline = newline or self._newline + encoding = encoding or self._encoding + encoding_errors = encoding_errors or self._encoding_errors + default_log_level = default_log_level or self._default_log_level + window_size = self._parse_window_size(window_size or self._window_size) + environ_user = environ_user or self._environ_user + if terminal_emulation is None: + terminal_emulation = self._terminal_emulation + terminal_type = terminal_type or self._terminal_type + telnetlib_log_level = telnetlib_log_level or self._telnetlib_log_level + if not prompt: + prompt, prompt_is_regexp = self._prompt + logger.info('Opening connection to %s:%s with prompt: %s%s' + % (host, port, prompt, ' (regexp)' if prompt_is_regexp else '')) + self._conn = self._get_connection(host, port, timeout, newline, + prompt, is_truthy(prompt_is_regexp), + encoding, encoding_errors, + default_log_level, + window_size, + environ_user, + is_truthy(terminal_emulation), + terminal_type, + telnetlib_log_level, + connection_timeout) + return self._cache.register(self._conn, alias) + + def _parse_window_size(self, window_size): + if not window_size: + return None + try: + cols, rows = window_size.split('x', 1) + return int(cols), int(rows) + except ValueError: + raise ValueError("Invalid window size '%s'. Should be " + "x." % window_size) + + def _get_connection(self, *args): + """Can be overridden to use a custom connection.""" + return TelnetConnection(*args) + + def _set_connection_timeout(self, connection_timeout): + self._connection_timeout = connection_timeout + if self._connection_timeout: + self._connection_timeout = timestr_to_secs(connection_timeout) + + def switch_connection(self, index_or_alias): + """Switches between active connections using an index or an alias. + + Aliases can be given to `Open Connection` keyword which also always + returns the connection index. + + This keyword returns the index of previous active connection. + + Example: + | `Open Connection` | myhost.net | | | + | `Login` | john | secret | | + | `Write` | some command | | | + | `Open Connection` | yourhost.com | 2nd conn | | + | `Login` | root | password | | + | `Write` | another cmd | | | + | ${old index}= | `Switch Connection` | 1 | # index | + | `Write` | something | | | + | `Switch Connection` | 2nd conn | | # alias | + | `Write` | whatever | | | + | `Switch Connection` | ${old index} | | # back to original | + | [Teardown] | `Close All Connections` | | | + + The example above expects that there were no other open + connections when opening the first one, because it used index + ``1`` when switching to the connection later. If you are not + sure about that, you can store the index into a variable as + shown below. + + | ${index} = | `Open Connection` | myhost.net | + | `Do Something` | | | + | `Switch Connection` | ${index} | | + """ + old_index = self._cache.current_index + self._conn = self._cache.switch(index_or_alias) + return old_index + + def close_all_connections(self): + """Closes all open connections and empties the connection cache. + + If multiple connections are opened, this keyword should be used in + a test or suite teardown to make sure that all connections are closed. + It is not an error is some of the connections have already been closed + by `Close Connection`. + + After this keyword, new indexes returned by `Open Connection` + keyword are reset to 1. + """ + self._conn = self._cache.close_all() + + +class TelnetConnection(telnetlib.Telnet): + NEW_ENVIRON_IS = b'\x00' + NEW_ENVIRON_VAR = b'\x00' + NEW_ENVIRON_VALUE = b'\x01' + INTERNAL_UPDATE_FREQUENCY = 0.03 + + def __init__(self, host=None, port=23, timeout=3.0, newline='CRLF', + prompt=None, prompt_is_regexp=False, + encoding='UTF-8', encoding_errors='ignore', + default_log_level='INFO', window_size=None, environ_user=None, + terminal_emulation=False, terminal_type=None, + telnetlib_log_level='TRACE', connection_timeout=None): + if connection_timeout is None: + telnetlib.Telnet.__init__(self, host, int(port) if port else 23) + else: + telnetlib.Telnet.__init__(self, host, int(port) if port else 23, + connection_timeout) + self._set_timeout(timeout) + self._set_newline(newline) + self._set_prompt(prompt, prompt_is_regexp) + self._set_encoding(encoding, encoding_errors) + self._set_default_log_level(default_log_level) + self._window_size = window_size + self._environ_user = self._encode(environ_user) if environ_user else None + self._terminal_emulator = self._check_terminal_emulation(terminal_emulation) + self._terminal_type = self._encode(terminal_type) if terminal_type else None + self.set_option_negotiation_callback(self._negotiate_options) + self._set_telnetlib_log_level(telnetlib_log_level) + self._opt_responses = list() + + def set_timeout(self, timeout): + """Sets the timeout used for waiting output in the current connection. + + Read operations that expect some output to appear (`Read Until`, `Read + Until Regexp`, `Read Until Prompt`, `Login`) use this timeout and fail + if the expected output does not appear before this timeout expires. + + The ``timeout`` must be given in `time string format`. The old timeout + is returned and can be used to restore the timeout later. + + Example: + | ${old} = | `Set Timeout` | 2 minute 30 seconds | + | `Do Something` | + | `Set Timeout` | ${old} | + + See `Configuration` section for more information about global and + connection specific configuration. + """ + self._verify_connection() + old = self._timeout + self._set_timeout(timeout) + return secs_to_timestr(old) + + def _set_timeout(self, timeout): + self._timeout = timestr_to_secs(timeout) + + def set_newline(self, newline): + """Sets the newline used by `Write` keyword in the current connection. + + The old newline is returned and can be used to restore the newline later. + See `Set Timeout` for a similar example. + + If terminal emulation is used, the newline can not be changed on an open + connection. + + See `Configuration` section for more information about global and + connection specific configuration. + """ + self._verify_connection() + if self._terminal_emulator: + raise AssertionError("Newline can not be changed when terminal emulation is used.") + old = self._newline + self._set_newline(newline) + return old + + def _set_newline(self, newline): + newline = str(newline).upper() + self._newline = newline.replace('LF', '\n').replace('CR', '\r') + + def set_prompt(self, prompt, prompt_is_regexp=False): + """Sets the prompt used by `Read Until Prompt` and `Login` in the current connection. + + If ``prompt_is_regexp`` is given a true value (see `Boolean arguments`), + the given ``prompt`` is considered to be a regular expression. + + The old prompt is returned and can be used to restore the prompt later. + + Example: + | ${prompt} | ${regexp} = | `Set Prompt` | $ | + | `Do Something` | + | `Set Prompt` | ${prompt} | ${regexp} | + + See the documentation of + [http://docs.python.org/library/re.html|Python re module] + for more information about the supported regular expression syntax. + Notice that possible backslashes need to be escaped in Robot Framework + test data. + + See `Configuration` section for more information about global and + connection specific configuration. + """ + self._verify_connection() + old = self._prompt + self._set_prompt(prompt, prompt_is_regexp) + if old[1]: + return old[0].pattern, True + return old + + def _set_prompt(self, prompt, prompt_is_regexp): + if is_truthy(prompt_is_regexp): + self._prompt = (re.compile(prompt), True) + else: + self._prompt = (prompt, False) + + def _prompt_is_set(self): + return self._prompt[0] is not None + + @keyword(types=None) + def set_encoding(self, encoding=None, errors=None): + """Sets the encoding to use for `writing and reading` in the current connection. + + The given ``encoding`` specifies the encoding to use when written/read + text is encoded/decoded, and ``errors`` specifies the error handler to + use if encoding/decoding fails. Either of these can be omitted and in + that case the old value is not affected. Use string ``NONE`` to disable + encoding altogether. + + See `Configuration` section for more information about encoding and + error handlers, as well as global and connection specific configuration + in general. + + The old values are returned and can be used to restore the encoding + and the error handler later. See `Set Prompt` for a similar example. + + If terminal emulation is used, the encoding can not be changed on an open + connection. + """ + self._verify_connection() + if self._terminal_emulator: + raise AssertionError("Encoding can not be changed when terminal emulation is used.") + old = self._encoding + self._set_encoding(encoding or old[0], errors or old[1]) + return old + + def _set_encoding(self, encoding, errors): + self._encoding = (encoding.upper(), errors) + + def _encode(self, text): + if is_bytes(text): + return text + if self._encoding[0] == 'NONE': + return text.encode('ASCII') + return text.encode(*self._encoding) + + def _decode(self, bytes): + if self._encoding[0] == 'NONE': + return bytes + return bytes.decode(*self._encoding) + + def set_telnetlib_log_level(self, level): + """Sets the log level used for `logging` in the underlying ``telnetlib``. + + Note that ``telnetlib`` can be very noisy thus using the level ``NONE`` + can shutdown the messages generated by this library. + """ + self._verify_connection() + old = self._telnetlib_log_level + self._set_telnetlib_log_level(level) + return old + + def _set_telnetlib_log_level(self, level): + if level.upper() == 'NONE': + self._telnetlib_log_level = 'NONE' + elif self._is_valid_log_level(level) is False: + raise AssertionError("Invalid log level '%s'" % level) + self._telnetlib_log_level = level.upper() + + def set_default_log_level(self, level): + """Sets the default log level used for `logging` in the current connection. + + The old default log level is returned and can be used to restore the + log level later. + + See `Configuration` section for more information about global and + connection specific configuration. + """ + self._verify_connection() + old = self._default_log_level + self._set_default_log_level(level) + return old + + def _set_default_log_level(self, level): + if level is None or not self._is_valid_log_level(level): + raise AssertionError("Invalid log level '%s'" % level) + self._default_log_level = level.upper() + + def _is_valid_log_level(self, level): + if level is None: + return True + if not is_string(level): + return False + return level.upper() in ('TRACE', 'DEBUG', 'INFO', 'WARN') + + def close_connection(self, loglevel=None): + """Closes the current Telnet connection. + + Remaining output in the connection is read, logged, and returned. + It is not an error to close an already closed connection. + + Use `Close All Connections` if you want to make sure all opened + connections are closed. + + See `Logging` section for more information about log levels. + """ + if self.sock: + self.sock.shutdown(socket.SHUT_RDWR) + self.close() + output = self._decode(self.read_all()) + self._log(output, loglevel) + return output + + def login(self, username, password, login_prompt='login: ', + password_prompt='Password: ', login_timeout='1 second', + login_incorrect='Login incorrect'): + """Logs in to the Telnet server with the given user information. + + This keyword reads from the connection until the ``login_prompt`` is + encountered and then types the given ``username``. Then it reads until + the ``password_prompt`` and types the given ``password``. In both cases + a newline is appended automatically and the connection specific + timeout used when waiting for outputs. + + How logging status is verified depends on whether a prompt is set for + this connection or not: + + 1) If the prompt is set, this keyword reads the output until the prompt + is found using the normal timeout. If no prompt is found, login is + considered failed and also this keyword fails. Note that in this case + both ``login_timeout`` and ``login_incorrect`` arguments are ignored. + + 2) If the prompt is not set, this keywords sleeps until ``login_timeout`` + and then reads all the output available on the connection. If the + output contains ``login_incorrect`` text, login is considered failed + and also this keyword fails. + + See `Configuration` section for more information about setting + newline, timeout, and prompt. + """ + output = self._submit_credentials(username, password, login_prompt, + password_prompt) + if self._prompt_is_set(): + success, output2 = self._read_until_prompt() + else: + success, output2 = self._verify_login_without_prompt( + login_timeout, login_incorrect) + output += output2 + self._log(output) + if not success: + raise AssertionError('Login incorrect') + return output + + def _submit_credentials(self, username, password, login_prompt, password_prompt): + # Using write_bare here instead of write because don't want to wait for + # newline: https://github.com/robotframework/robotframework/issues/1371 + output = self.read_until(login_prompt, 'TRACE') + self.write_bare(username + self._newline) + output += self.read_until(password_prompt, 'TRACE') + self.write_bare(password + self._newline) + return output + + def _verify_login_without_prompt(self, delay, incorrect): + time.sleep(timestr_to_secs(delay)) + output = self.read('TRACE') + success = incorrect not in output + return success, output + + def write(self, text, loglevel=None): + """Writes the given text plus a newline into the connection. + + The newline character sequence to use can be [#Configuration|configured] + both globally and per connection basis. The default value is ``CRLF``. + + This keyword consumes the written text, until the added newline, from + the output and logs and returns it. The given text itself must not + contain newlines. Use `Write Bare` instead if either of these features + causes a problem. + + *Note:* This keyword does not return the possible output of the executed + command. To get the output, one of the `Read ...` `keywords` must be + used. See `Writing and reading` section for more details. + + See `Logging` section for more information about log levels. + """ + newline = self._get_newline_for(text) + if newline in text: + raise RuntimeError("'Write' keyword cannot be used with strings " + "containing newlines. Use 'Write Bare' instead.") + self.write_bare(text + newline) + # Can't read until 'text' because long lines are cut strangely in the output + return self.read_until(self._newline, loglevel) + + def _get_newline_for(self, text): + if is_bytes(text): + return self._encode(self._newline) + return self._newline + + def write_bare(self, text): + """Writes the given text, and nothing else, into the connection. + + This keyword does not append a newline nor consume the written text. + Use `Write` if these features are needed. + """ + self._verify_connection() + telnetlib.Telnet.write(self, self._encode(text)) + + def write_until_expected_output(self, text, expected, timeout, + retry_interval, loglevel=None): + """Writes the given ``text`` repeatedly, until ``expected`` appears in the output. + + ``text`` is written without appending a newline and it is consumed from + the output before trying to find ``expected``. If ``expected`` does not + appear in the output within ``timeout``, this keyword fails. + + ``retry_interval`` defines the time to wait ``expected`` to appear before + writing the ``text`` again. Consuming the written ``text`` is subject to + the normal [#Configuration|configured timeout]. + + Both ``timeout`` and ``retry_interval`` must be given in `time string + format`. See `Logging` section for more information about log levels. + + Example: + | Write Until Expected Output | ps -ef| grep myprocess\\r\\n | myprocess | + | ... | 5 s | 0.5 s | + + The above example writes command ``ps -ef | grep myprocess\\r\\n`` until + ``myprocess`` appears in the output. The command is written every 0.5 + seconds and the keyword fails if ``myprocess`` does not appear in + the output in 5 seconds. + """ + timeout = timestr_to_secs(timeout) + retry_interval = timestr_to_secs(retry_interval) + maxtime = time.time() + timeout + while time.time() < maxtime: + self.write_bare(text) + self.read_until(text, loglevel) + try: + with self._custom_timeout(retry_interval): + return self.read_until(expected, loglevel) + except AssertionError: + pass + raise NoMatchError(expected, timeout) + + def write_control_character(self, character): + """Writes the given control character into the connection. + + The control character is prepended with an IAC (interpret as command) + character. + + The following control character names are supported: BRK, IP, AO, AYT, + EC, EL, NOP. Additionally, you can use arbitrary numbers to send any + control character. + + Example: + | Write Control Character | BRK | # Send Break command | + | Write Control Character | 241 | # Send No operation command | + """ + self._verify_connection() + self.sock.sendall(telnetlib.IAC + self._get_control_character(character)) + + def _get_control_character(self, int_or_name): + try: + ordinal = int(int_or_name) + return bytes(bytearray([ordinal])) + except ValueError: + return self._convert_control_code_name_to_character(int_or_name) + + def _convert_control_code_name_to_character(self, name): + code_names = { + 'BRK' : telnetlib.BRK, + 'IP' : telnetlib.IP, + 'AO' : telnetlib.AO, + 'AYT' : telnetlib.AYT, + 'EC' : telnetlib.EC, + 'EL' : telnetlib.EL, + 'NOP' : telnetlib.NOP + } + try: + return code_names[name] + except KeyError: + raise RuntimeError("Unsupported control character '%s'." % name) + + def read(self, loglevel=None): + """Reads everything that is currently available in the output. + + Read output is both returned and logged. See `Logging` section for more + information about log levels. + """ + self._verify_connection() + output = self._decode(self.read_very_eager()) + if self._terminal_emulator: + self._terminal_emulator.feed(output) + output = self._terminal_emulator.read() + self._log(output, loglevel) + return output + + def read_until(self, expected, loglevel=None): + """Reads output until ``expected`` text is encountered. + + Text up to and including the match is returned and logged. If no match + is found, this keyword fails. How much to wait for the output depends + on the [#Configuration|configured timeout]. + + See `Logging` section for more information about log levels. Use + `Read Until Regexp` if more complex matching is needed. + """ + success, output = self._read_until(expected) + self._log(output, loglevel) + if not success: + raise NoMatchError(expected, self._timeout, output) + return output + + def _read_until(self, expected): + self._verify_connection() + if self._terminal_emulator: + return self._terminal_read_until(expected) + expected = self._encode(expected) + output = telnetlib.Telnet.read_until(self, expected, self._timeout) + return output.endswith(expected), self._decode(output) + + @property + def _terminal_frequency(self): + return min(self.INTERNAL_UPDATE_FREQUENCY, self._timeout) + + def _terminal_read_until(self, expected): + max_time = time.time() + self._timeout + output = self._terminal_emulator.read_until(expected) + if output: + return True, output + while time.time() < max_time: + output = telnetlib.Telnet.read_until(self, self._encode(expected), + self._terminal_frequency) + self._terminal_emulator.feed(self._decode(output)) + output = self._terminal_emulator.read_until(expected) + if output: + return True, output + return False, self._terminal_emulator.read() + + def _read_until_regexp(self, *expected): + self._verify_connection() + if self._terminal_emulator: + return self._terminal_read_until_regexp(expected) + expected = [self._encode(exp) if is_unicode(exp) else exp + for exp in expected] + return self._telnet_read_until_regexp(expected) + + def _terminal_read_until_regexp(self, expected_list): + max_time = time.time() + self._timeout + regexps_bytes = [self._to_byte_regexp(rgx) for rgx in expected_list] + regexps_unicode = [re.compile(self._decode(rgx.pattern)) + for rgx in regexps_bytes] + out = self._terminal_emulator.read_until_regexp(regexps_unicode) + if out: + return True, out + while time.time() < max_time: + output = self.expect(regexps_bytes, self._terminal_frequency)[-1] + self._terminal_emulator.feed(self._decode(output)) + out = self._terminal_emulator.read_until_regexp(regexps_unicode) + if out: + return True, out + return False, self._terminal_emulator.read() + + def _telnet_read_until_regexp(self, expected_list): + expected = [self._to_byte_regexp(exp) for exp in expected_list] + try: + index, _, output = self.expect(expected, self._timeout) + except TypeError: + index, output = -1, b'' + return index != -1, self._decode(output) + + def _to_byte_regexp(self, exp): + if is_bytes(exp): + return re.compile(exp) + if is_string(exp): + return re.compile(self._encode(exp)) + pattern = exp.pattern + if is_bytes(pattern): + return exp + return re.compile(self._encode(pattern)) + + def read_until_regexp(self, *expected): + """Reads output until any of the ``expected`` regular expressions match. + + This keyword accepts any number of regular expressions patterns or + compiled Python regular expression objects as arguments. Text up to + and including the first match to any of the regular expressions is + returned and logged. If no match is found, this keyword fails. How much + to wait for the output depends on the [#Configuration|configured timeout]. + + If the last given argument is a [#Logging|valid log level], it is used + as ``loglevel`` similarly as with `Read Until` keyword. + + See the documentation of + [http://docs.python.org/library/re.html|Python re module] + for more information about the supported regular expression syntax. + Notice that possible backslashes need to be escaped in Robot Framework + test data. + + Examples: + | `Read Until Regexp` | (#|$) | + | `Read Until Regexp` | first_regexp | second_regexp | + | `Read Until Regexp` | \\\\d{4}-\\\\d{2}-\\\\d{2} | DEBUG | + """ + if not expected: + raise RuntimeError('At least one pattern required') + if self._is_valid_log_level(expected[-1]): + loglevel = expected[-1] + expected = expected[:-1] + else: + loglevel = None + success, output = self._read_until_regexp(*expected) + self._log(output, loglevel) + if not success: + expected = [exp if is_string(exp) else exp.pattern + for exp in expected] + raise NoMatchError(expected, self._timeout, output) + return output + + def read_until_prompt(self, loglevel=None, strip_prompt=False): + """Reads output until the prompt is encountered. + + This keyword requires the prompt to be [#Configuration|configured] + either in `importing` or with `Open Connection` or `Set Prompt` keyword. + + By default, text up to and including the prompt is returned and logged. + If no prompt is found, this keyword fails. How much to wait for the + output depends on the [#Configuration|configured timeout]. + + If you want to exclude the prompt from the returned output, set + ``strip_prompt`` to a true value (see `Boolean arguments`). If your + prompt is a regular expression, make sure that the expression spans the + whole prompt, because only the part of the output that matches the + regular expression is stripped away. + + See `Logging` section for more information about log levels. + """ + if not self._prompt_is_set(): + raise RuntimeError('Prompt is not set.') + success, output = self._read_until_prompt() + self._log(output, loglevel) + if not success: + prompt, regexp = self._prompt + raise AssertionError("Prompt '%s' not found in %s." + % (prompt if not regexp else prompt.pattern, + secs_to_timestr(self._timeout))) + if is_truthy(strip_prompt): + output = self._strip_prompt(output) + return output + + def _read_until_prompt(self): + prompt, regexp = self._prompt + read_until = self._read_until_regexp if regexp else self._read_until + return read_until(prompt) + + def _strip_prompt(self, output): + prompt, regexp = self._prompt + if not regexp: + length = len(prompt) + else: + match = prompt.search(output) + length = match.end() - match.start() + return output[:-length] + + def execute_command(self, command, loglevel=None, strip_prompt=False): + """Executes the given ``command`` and reads, logs, and returns everything until the prompt. + + This keyword requires the prompt to be [#Configuration|configured] + either in `importing` or with `Open Connection` or `Set Prompt` keyword. + + This is a convenience keyword that uses `Write` and `Read Until Prompt` + internally. Following two examples are thus functionally identical: + + | ${out} = | `Execute Command` | pwd | + + | `Write` | pwd | + | ${out} = | `Read Until Prompt` | + + See `Logging` section for more information about log levels and `Read + Until Prompt` for more information about the ``strip_prompt`` parameter. + """ + self.write(command, loglevel) + return self.read_until_prompt(loglevel, strip_prompt) + + @contextmanager + def _custom_timeout(self, timeout): + old = self.set_timeout(timeout) + try: + yield + finally: + self.set_timeout(old) + + def _verify_connection(self): + if not self.sock: + raise RuntimeError('No connection open') + + def _log(self, msg, level=None): + msg = msg.strip() + if msg: + logger.write(msg, level or self._default_log_level) + + def _negotiate_options(self, sock, cmd, opt): + # We don't have state changes in our accepted telnet options. + # Therefore, we just track if we've already responded to an option. If + # this is the case, we must not send any response. + if cmd in (telnetlib.DO, telnetlib.DONT, telnetlib.WILL, telnetlib.WONT): + if (cmd, opt) in self._opt_responses: + return + else: + self._opt_responses.append((cmd, opt)) + + # This is supposed to turn server side echoing on and turn other options off. + if opt == telnetlib.ECHO and cmd in (telnetlib.WILL, telnetlib.WONT): + self._opt_echo_on(opt) + elif cmd == telnetlib.DO and opt == telnetlib.TTYPE and self._terminal_type: + self._opt_terminal_type(opt, self._terminal_type) + elif cmd == telnetlib.DO and opt == telnetlib.NEW_ENVIRON and self._environ_user: + self._opt_environ_user(opt, self._environ_user) + elif cmd == telnetlib.DO and opt == telnetlib.NAWS and self._window_size: + self._opt_window_size(opt, *self._window_size) + elif opt != telnetlib.NOOPT: + self._opt_dont_and_wont(cmd, opt) + + def _opt_echo_on(self, opt): + return self.sock.sendall(telnetlib.IAC + telnetlib.DO + opt) + + def _opt_terminal_type(self, opt, terminal_type): + self.sock.sendall(telnetlib.IAC + telnetlib.WILL + opt) + self.sock.sendall(telnetlib.IAC + telnetlib.SB + telnetlib.TTYPE + + self.NEW_ENVIRON_IS + terminal_type + + telnetlib.IAC + telnetlib.SE) + + def _opt_environ_user(self, opt, environ_user): + self.sock.sendall(telnetlib.IAC + telnetlib.WILL + opt) + self.sock.sendall(telnetlib.IAC + telnetlib.SB + telnetlib.NEW_ENVIRON + + self.NEW_ENVIRON_IS + self.NEW_ENVIRON_VAR + + b"USER" + self.NEW_ENVIRON_VALUE + environ_user + + telnetlib.IAC + telnetlib.SE) + + def _opt_window_size(self, opt, window_x, window_y): + self.sock.sendall(telnetlib.IAC + telnetlib.WILL + opt) + self.sock.sendall(telnetlib.IAC + telnetlib.SB + telnetlib.NAWS + + struct.pack('!HH', window_x, window_y) + + telnetlib.IAC + telnetlib.SE) + + def _opt_dont_and_wont(self, cmd, opt): + if cmd in (telnetlib.DO, telnetlib.DONT): + self.sock.sendall(telnetlib.IAC + telnetlib.WONT + opt) + elif cmd in (telnetlib.WILL, telnetlib.WONT): + self.sock.sendall(telnetlib.IAC + telnetlib.DONT + opt) + + def msg(self, msg, *args): + # Forward telnetlib's debug messages to log + if self._telnetlib_log_level != 'NONE': + logger.write(msg % args, self._telnetlib_log_level) + + def _check_terminal_emulation(self, terminal_emulation): + if not terminal_emulation: + return False + if not pyte: + raise RuntimeError("Terminal emulation requires pyte module!\n" + "http://pypi.python.org/pypi/pyte/") + return TerminalEmulator(window_size=self._window_size, + newline=self._newline) + + +class TerminalEmulator(object): + + def __init__(self, window_size=None, newline="\r\n"): + self._rows, self._columns = window_size or (200, 200) + self._newline = newline + self._stream = pyte.Stream() + self._screen = pyte.HistoryScreen(self._rows, + self._columns, + history=100000) + self._stream.attach(self._screen) + self._buffer = '' + self._whitespace_after_last_feed = '' + + @property + def current_output(self): + return self._buffer + self._dump_screen() + + def _dump_screen(self): + return self._get_history(self._screen) + \ + self._get_screen(self._screen) + \ + self._whitespace_after_last_feed + + def _get_history(self, screen): + if not screen.history.top: + return '' + rows = [] + for row in screen.history.top: + # Newer pyte versions store row data in mappings + data = (char.data for _, char in sorted(row.items())) + rows.append(''.join(data).rstrip()) + return self._newline.join(rows).rstrip(self._newline) + self._newline + + def _get_screen(self, screen): + rows = (row.rstrip() for row in screen.display) + return self._newline.join(rows).rstrip(self._newline) + + def feed(self, text): + self._stream.feed(text) + self._whitespace_after_last_feed = text[len(text.rstrip()):] + + def read(self): + current_out = self.current_output + self._update_buffer('') + return current_out + + def read_until(self, expected): + current_out = self.current_output + exp_index = current_out.find(expected) + if exp_index != -1: + self._update_buffer(current_out[exp_index+len(expected):]) + return current_out[:exp_index+len(expected)] + return None + + def read_until_regexp(self, regexp_list): + current_out = self.current_output + for rgx in regexp_list: + match = rgx.search(current_out) + if match: + self._update_buffer(current_out[match.end():]) + return current_out[:match.end()] + return None + + def _update_buffer(self, terminal_buffer): + self._buffer = terminal_buffer + self._whitespace_after_last_feed = '' + self._screen.reset() + + +class NoMatchError(AssertionError): + ROBOT_SUPPRESS_NAME = True + + def __init__(self, expected, timeout, output=None): + self.expected = expected + self.timeout = secs_to_timestr(timeout) + self.output = output + AssertionError.__init__(self, self._get_message()) + + def _get_message(self): + expected = "'%s'" % self.expected \ + if is_string(self.expected) \ + else seq2str(self.expected, lastsep=' or ') + msg = "No match found for %s in %s." % (expected, self.timeout) + if self.output is not None: + msg += ' Output:\n%s' % self.output + return msg diff --git a/robot/lib/python3.8/site-packages/robot/libraries/XML.py b/robot/lib/python3.8/site-packages/robot/libraries/XML.py new file mode 100644 index 0000000000000000000000000000000000000000..527b223cdbf9a8b23da6d47b44be9de4e84feec8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/libraries/XML.py @@ -0,0 +1,1506 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy +import re +import os + +try: + from lxml import etree as lxml_etree +except ImportError: + lxml_etree = None + +from robot.api import logger +from robot.libraries.BuiltIn import BuiltIn +from robot.utils import (asserts, ET, ETSource, is_bytes, is_falsy, is_string, + is_truthy, plural_or_not as s, PY2) +from robot.version import get_version + + +should_be_equal = asserts.assert_equal +should_match = BuiltIn().should_match + + +class XML(object): + """Robot Framework test library for verifying and modifying XML documents. + + As the name implies, _XML_ is a test library for verifying contents of XML + files. In practice it is a pretty thin wrapper on top of Python's + [http://docs.python.org/library/xml.etree.elementtree.html|ElementTree XML API]. + + The library has the following main usages: + + - Parsing an XML file, or a string containing XML, into an XML element + structure and finding certain elements from it for for further analysis + (e.g. `Parse XML` and `Get Element` keywords). + - Getting text or attributes of elements + (e.g. `Get Element Text` and `Get Element Attribute`). + - Directly verifying text, attributes, or whole elements + (e.g `Element Text Should Be` and `Elements Should Be Equal`). + - Modifying XML and saving it (e.g. `Set Element Text`, `Add Element` + and `Save XML`). + + == Table of contents == + + %TOC% + + = Parsing XML = + + XML can be parsed into an element structure using `Parse XML` keyword. + The XML to be parsed can be specified using a path to an XML file or as + a string or bytes that contain XML directly. The keyword returns the root + element of the structure, which then contains other elements as its + children and their children. Possible comments and processing instructions + in the source XML are removed. + + XML is not validated during parsing even if has a schema defined. How + possible doctype elements are handled otherwise depends on the used XML + module and on the platform. The standard ElementTree strips doctypes + altogether but when `using lxml` they are preserved when XML is saved. + + The element structure returned by `Parse XML`, as well as elements + returned by keywords such as `Get Element`, can be used as the ``source`` + argument with other keywords. In addition to an already parsed XML + structure, other keywords also accept paths to XML files and strings + containing XML similarly as `Parse XML`. Notice that keywords that modify + XML do not write those changes back to disk even if the source would be + given as a path to a file. Changes must always saved explicitly using + `Save XML` keyword. + + When the source is given as a path to a file, the forward slash character + (``/``) can be used as the path separator regardless the operating system. + On Windows also the backslash works, but it the test data it needs to be + escaped by doubling it (``\\\\``). Using the built-in variable ``${/}`` + naturally works too. + + Note: Support for XML as bytes is new in Robot Framework 3.2. + + = Using lxml = + + By default this library uses Python's standard + [http://docs.python.org/library/xml.etree.elementtree.html|ElementTree] + module for parsing XML, but it can be configured to use + [http://lxml.de|lxml] module instead when `importing` the library. + The resulting element structure has same API regardless which module + is used for parsing. + + The main benefits of using lxml is that it supports richer xpath syntax + than the standard ElementTree and enables using `Evaluate Xpath` keyword. + It also preserves the doctype and possible namespace prefixes saving XML. + + = Example = + + The following simple example demonstrates parsing XML and verifying its + contents both using keywords in this library and in _BuiltIn_ and + _Collections_ libraries. How to use xpath expressions to find elements + and what attributes the returned elements contain are discussed, with + more examples, in `Finding elements with xpath` and `Element attributes` + sections. + + In this example, as well as in many other examples in this documentation, + ``${XML}`` refers to the following example XML document. In practice + ``${XML}`` could either be a path to an XML file or it could contain the XML + itself. + + | + | text + | + | + | + | + | more text + | + | + | + | + |

+ | Text with bold and italics. + |

+ | + |
+ + | ${root} = | `Parse XML` | ${XML} | | | + | `Should Be Equal` | ${root.tag} | example | | | + | ${first} = | `Get Element` | ${root} | first | | + | `Should Be Equal` | ${first.text} | text | | | + | `Dictionary Should Contain Key` | ${first.attrib} | id | | + | `Element Text Should Be` | ${first} | text | | | + | `Element Attribute Should Be` | ${first} | id | 1 | | + | `Element Attribute Should Be` | ${root} | id | 1 | xpath=first | + | `Element Attribute Should Be` | ${XML} | id | 1 | xpath=first | + + Notice that in the example three last lines are equivalent. Which one to + use in practice depends on which other elements you need to get or verify. + If you only need to do one verification, using the last line alone would + suffice. If more verifications are needed, parsing the XML with `Parse XML` + only once would be more efficient. + + = Finding elements with xpath = + + ElementTree, and thus also this library, supports finding elements using + xpath expressions. ElementTree does not, however, support the full xpath + standard. The supported xpath syntax is explained below and + [https://docs.python.org/library/xml.etree.elementtree.html#xpath-support| + ElementTree documentation] provides more details. In the examples + ``${XML}`` refers to the same XML structure as in the earlier example. + + If lxml support is enabled when `importing` the library, the whole + [http://www.w3.org/TR/xpath/|xpath 1.0 standard] is supported. + That includes everything listed below but also lot of other useful + constructs. + + == Tag names == + + When just a single tag name is used, xpath matches all direct child + elements that have that tag name. + + | ${elem} = | `Get Element` | ${XML} | third | + | `Should Be Equal` | ${elem.tag} | third | | + | @{children} = | `Get Elements` | ${elem} | child | + | `Length Should Be` | ${children} | 2 | | + + == Paths == + + Paths are created by combining tag names with a forward slash (``/``). For + example, ``parent/child`` matches all ``child`` elements under ``parent`` + element. Notice that if there are multiple ``parent`` elements that all + have ``child`` elements, ``parent/child`` xpath will match all these + ``child`` elements. + + | ${elem} = | `Get Element` | ${XML} | second/child | + | `Should Be Equal` | ${elem.tag} | child | | + | ${elem} = | `Get Element` | ${XML} | third/child/grandchild | + | `Should Be Equal` | ${elem.tag} | grandchild | | + + == Wildcards == + + An asterisk (``*``) can be used in paths instead of a tag name to denote + any element. + + | @{children} = | `Get Elements` | ${XML} | */child | + | `Length Should Be` | ${children} | 3 | | + + == Current element == + + The current element is denoted with a dot (``.``). Normally the current + element is implicit and does not need to be included in the xpath. + + == Parent element == + + The parent element of another element is denoted with two dots (``..``). + Notice that it is not possible to refer to the parent of the current + element. + + | ${elem} = | `Get Element` | ${XML} | */second/.. | + | `Should Be Equal` | ${elem.tag} | third | | + + == Search all sub elements == + + Two forward slashes (``//``) mean that all sub elements, not only the + direct children, are searched. If the search is started from the current + element, an explicit dot is required. + + | @{elements} = | `Get Elements` | ${XML} | .//second | + | `Length Should Be` | ${elements} | 2 | | + | ${b} = | `Get Element` | ${XML} | html//b | + | `Should Be Equal` | ${b.text} | bold | | + + == Predicates == + + Predicates allow selecting elements using also other criteria than tag + names, for example, attributes or position. They are specified after the + normal tag name or path using syntax ``path[predicate]``. The path can have + wildcards and other special syntax explained earlier. What predicates + the standard ElementTree supports is explained in the table below. + + | = Predicate = | = Matches = | = Example = | + | @attrib | Elements with attribute ``attrib``. | second[@id] | + | @attrib="value" | Elements with attribute ``attrib`` having value ``value``. | *[@id="2"] | + | position | Elements at the specified position. Position can be an integer (starting from 1), expression ``last()``, or relative expression like ``last() - 1``. | third/child[1] | + | tag | Elements with a child element named ``tag``. | third/child[grandchild] | + + Predicates can also be stacked like ``path[predicate1][predicate2]``. + A limitation is that possible position predicate must always be first. + + = Element attributes = + + All keywords returning elements, such as `Parse XML`, and `Get Element`, + return ElementTree's + [http://docs.python.org/library/xml.etree.elementtree.html#element-objects|Element objects]. + These elements can be used as inputs for other keywords, but they also + contain several useful attributes that can be accessed directly using + the extended variable syntax. + + The attributes that are both useful and convenient to use in the test + data are explained below. Also other attributes, including methods, can + be accessed, but that is typically better to do in custom libraries than + directly in the test data. + + The examples use the same ``${XML}`` structure as the earlier examples. + + == tag == + + The tag of the element. + + | ${root} = | `Parse XML` | ${XML} | + | `Should Be Equal` | ${root.tag} | example | + + == text == + + The text that the element contains or Python ``None`` if the element has no + text. Notice that the text _does not_ contain texts of possible child + elements nor text after or between children. Notice also that in XML + whitespace is significant, so the text contains also possible indentation + and newlines. To get also text of the possible children, optionally + whitespace normalized, use `Get Element Text` keyword. + + | ${1st} = | `Get Element` | ${XML} | first | + | `Should Be Equal` | ${1st.text} | text | | + | ${2nd} = | `Get Element` | ${XML} | second/child | + | `Should Be Equal` | ${2nd.text} | ${NONE} | | + | ${p} = | `Get Element` | ${XML} | html/p | + | `Should Be Equal` | ${p.text} | \\n${SPACE*6}Text with${SPACE} | + + == tail == + + The text after the element before the next opening or closing tag. Python + ``None`` if the element has no tail. Similarly as with ``text``, also + ``tail`` contains possible indentation and newlines. + + | ${b} = | `Get Element` | ${XML} | html/p/b | + | `Should Be Equal` | ${b.tail} | ${SPACE}and${SPACE} | + + == attrib == + + A Python dictionary containing attributes of the element. + + | ${2nd} = | `Get Element` | ${XML} | second | + | `Should Be Equal` | ${2nd.attrib['id']} | 2 | | + | ${3rd} = | `Get Element` | ${XML} | third | + | `Should Be Empty` | ${3rd.attrib} | | | + + = Handling XML namespaces = + + ElementTree and lxml handle possible namespaces in XML documents by adding + the namespace URI to tag names in so called Clark Notation. That is + inconvenient especially with xpaths, and by default this library strips + those namespaces away and moves them to ``xmlns`` attribute instead. That + can be avoided by passing ``keep_clark_notation`` argument to `Parse XML` + keyword. Alternatively `Parse XML` supports stripping namespace information + altogether by using ``strip_namespaces`` argument. The pros and cons of + different approaches are discussed in more detail below. + + == How ElementTree handles namespaces == + + If an XML document has namespaces, ElementTree adds namespace information + to tag names in [http://www.jclark.com/xml/xmlns.htm|Clark Notation] + (e.g. ``{http://ns.uri}tag``) and removes original ``xmlns`` attributes. + This is done both with default namespaces and with namespaces with a prefix. + How it works in practice is illustrated by the following example, where + ``${NS}`` variable contains this XML document: + + | + | + | + | + | + + | ${root} = | `Parse XML` | ${NS} | keep_clark_notation=yes | + | `Should Be Equal` | ${root.tag} | {http://www.w3.org/1999/XSL/Transform}stylesheet | + | `Element Should Exist` | ${root} | {http://www.w3.org/1999/XSL/Transform}template/{http://www.w3.org/1999/xhtml}html | + | `Should Be Empty` | ${root.attrib} | + + As you can see, including the namespace URI in tag names makes xpaths + really long and complex. + + If you save the XML, ElementTree moves namespace information back to + ``xmlns`` attributes. Unfortunately it does not restore the original + prefixes: + + | + | + | + | + | + + The resulting output is semantically same as the original, but mangling + prefixes like this may still not be desirable. Notice also that the actual + output depends slightly on ElementTree version. + + == Default namespace handling == + + Because the way ElementTree handles namespaces makes xpaths so complicated, + this library, by default, strips namespaces from tag names and moves that + information back to ``xmlns`` attributes. How this works in practice is + shown by the example below, where ``${NS}`` variable contains the same XML + document as in the previous example. + + | ${root} = | `Parse XML` | ${NS} | + | `Should Be Equal` | ${root.tag} | stylesheet | + | `Element Should Exist` | ${root} | template/html | + | `Element Attribute Should Be` | ${root} | xmlns | http://www.w3.org/1999/XSL/Transform | + | `Element Attribute Should Be` | ${root} | xmlns | http://www.w3.org/1999/xhtml | xpath=template/html | + + Now that tags do not contain namespace information, xpaths are simple again. + + A minor limitation of this approach is that namespace prefixes are lost. + As a result the saved output is not exactly same as the original one in + this case either: + + | + | + | + + Also this output is semantically same as the original. If the original XML + had only default namespaces, the output would also look identical. + + == Namespaces when using lxml == + + This library handles namespaces same way both when `using lxml` and when + not using it. There are, however, differences how lxml internally handles + namespaces compared to the standard ElementTree. The main difference is + that lxml stores information about namespace prefixes and they are thus + preserved if XML is saved. Another visible difference is that lxml includes + namespace information in child elements got with `Get Element` if the + parent element has namespaces. + + == Stripping namespaces altogether == + + Because namespaces often add unnecessary complexity, `Parse XML` supports + stripping them altogether by using ``strip_namespaces=True``. When this + option is enabled, namespaces are not shown anywhere nor are they included + if XML is saved. + + == Attribute namespaces == + + Attributes in XML documents are, by default, in the same namespaces as + the element they belong to. It is possible to use different namespaces + by using prefixes, but this is pretty rare. + + If an attribute has a namespace prefix, ElementTree will replace it with + Clark Notation the same way it handles elements. Because stripping + namespaces from attributes could cause attribute conflicts, this library + does not handle attribute namespaces at all. Thus the following example + works the same way regardless how namespaces are handled. + + | ${root} = | `Parse XML` | | + | `Element Attribute Should Be` | ${root} | id | 1 | + | `Element Attribute Should Be` | ${root} | {http://my.ns}id | 2 | + + = Boolean arguments = + + Some keywords accept arguments that are handled as Boolean values true or + false. If such an argument is given as a string, it is considered false if + it is an empty string or equal to ``FALSE``, ``NONE``, ``NO``, ``OFF`` or + ``0``, case-insensitively. Other strings are considered true regardless + their value, and other argument types are tested using the same + [http://docs.python.org/library/stdtypes.html#truth|rules as in Python]. + + True examples: + | `Parse XML` | ${XML} | keep_clark_notation=True | # Strings are generally true. | + | `Parse XML` | ${XML} | keep_clark_notation=yes | # Same as the above. | + | `Parse XML` | ${XML} | keep_clark_notation=${TRUE} | # Python ``True`` is true. | + | `Parse XML` | ${XML} | keep_clark_notation=${42} | # Numbers other than 0 are true. | + + False examples: + | `Parse XML` | ${XML} | keep_clark_notation=False | # String ``false`` is false. | + | `Parse XML` | ${XML} | keep_clark_notation=no | # Also string ``no`` is false. | + | `Parse XML` | ${XML} | keep_clark_notation=${EMPTY} | # Empty string is false. | + | `Parse XML` | ${XML} | keep_clark_notation=${FALSE} | # Python ``False`` is false. | + + Considering string ``NONE`` false is new in Robot Framework 3.0.3 and + considering also ``OFF`` and ``0`` false is new in Robot Framework 3.1. + + == Pattern matching == + + Some keywords, for example `Elements Should Match`, support so called + [http://en.wikipedia.org/wiki/Glob_(programming)|glob patterns] where: + + | ``*`` | matches any string, even an empty string | + | ``?`` | matches any single character | + | ``[chars]`` | matches one character in the bracket | + | ``[!chars]`` | matches one character not in the bracket | + | ``[a-z]`` | matches one character from the range in the bracket | + | ``[!a-z]`` | matches one character not from the range in the bracket | + + Unlike with glob patterns normally, path separator characters ``/`` and + ``\\`` and the newline character ``\\n`` are matches by the above + wildcards. + + Support for brackets like ``[abc]`` and ``[!a-z]`` is new in + Robot Framework 3.1 + """ + ROBOT_LIBRARY_SCOPE = 'GLOBAL' + ROBOT_LIBRARY_VERSION = get_version() + _xml_declaration = re.compile('^<\?xml .*\?>') + + def __init__(self, use_lxml=False): + """Import library with optionally lxml mode enabled. + + By default this library uses Python's standard + [http://docs.python.org/library/xml.etree.elementtree.html|ElementTree] + module for parsing XML. If ``use_lxml`` argument is given a true value + (see `Boolean arguments`), the library will use [http://lxml.de|lxml] + module instead. See `Using lxml` section for benefits provided by lxml. + + Using lxml requires that the lxml module is installed on the system. + If lxml mode is enabled but the module is not installed, this library + will emit a warning and revert back to using the standard ElementTree. + """ + use_lxml = is_truthy(use_lxml) + if use_lxml and lxml_etree: + self.etree = lxml_etree + self.modern_etree = True + self.lxml_etree = True + else: + self.etree = ET + self.modern_etree = ET.VERSION >= '1.3' + self.lxml_etree = False + if use_lxml and not lxml_etree: + logger.warn('XML library reverted to use standard ElementTree ' + 'because lxml module is not installed.') + self._ns_stripper = NameSpaceStripper(self.etree, self.lxml_etree) + + def parse_xml(self, source, keep_clark_notation=False, strip_namespaces=False): + """Parses the given XML file or string into an element structure. + + The ``source`` can either be a path to an XML file or a string + containing XML. In both cases the XML is parsed into ElementTree + [http://docs.python.org/library/xml.etree.elementtree.html#element-objects|element structure] + and the root element is returned. Possible comments and processing + instructions in the source XML are removed. + + As discussed in `Handling XML namespaces` section, this keyword, by + default, removes namespace information ElementTree has added to tag + names and moves it into ``xmlns`` attributes. This typically eases + handling XML documents with namespaces considerably. If you do not + want that to happen, or want to avoid the small overhead of going + through the element structure when your XML does not have namespaces, + you can disable this feature by giving ``keep_clark_notation`` argument + a true value (see `Boolean arguments`). + + If you want to strip namespace information altogether so that it is + not included even if XML is saved, you can give a true value to + ``strip_namespaces`` argument. This functionality is new in Robot + Framework 3.0.2. + + Examples: + | ${root} = | Parse XML | | + | ${xml} = | Parse XML | ${CURDIR}/test.xml | keep_clark_notation=True | + | ${xml} = | Parse XML | ${CURDIR}/test.xml | strip_namespaces=True | + + Use `Get Element` keyword if you want to get a certain element and not + the whole structure. See `Parsing XML` section for more details and + examples. + """ + with ETSource(source) as source: + tree = self.etree.parse(source) + if self.lxml_etree: + strip = (lxml_etree.Comment, lxml_etree.ProcessingInstruction) + lxml_etree.strip_elements(tree, *strip, **dict(with_tail=False)) + root = tree.getroot() + if not is_truthy(keep_clark_notation): + self._ns_stripper.strip(root, preserve=is_falsy(strip_namespaces)) + return root + + def get_element(self, source, xpath='.'): + """Returns an element in the ``source`` matching the ``xpath``. + + The ``source`` can be a path to an XML file, a string containing XML, or + an already parsed XML element. The ``xpath`` specifies which element to + find. See the `introduction` for more details about both the possible + sources and the supported xpath syntax. + + The keyword fails if more, or less, than one element matches the + ``xpath``. Use `Get Elements` if you want all matching elements to be + returned. + + Examples using ``${XML}`` structure from `Example`: + | ${element} = | Get Element | ${XML} | second | + | ${child} = | Get Element | ${element} | child | + + `Parse XML` is recommended for parsing XML when the whole structure + is needed. It must be used if there is a need to configure how XML + namespaces are handled. + + Many other keywords use this keyword internally, and keywords modifying + XML are typically documented to both to modify the given source and + to return it. Modifying the source does not apply if the source is + given as a string. The XML structure parsed based on the string and + then modified is nevertheless returned. + """ + elements = self.get_elements(source, xpath) + if len(elements) != 1: + self._raise_wrong_number_of_matches(len(elements), xpath) + return elements[0] + + def _raise_wrong_number_of_matches(self, count, xpath, message=None): + if not message: + message = self._wrong_number_of_matches(count, xpath) + raise AssertionError(message) + + def _wrong_number_of_matches(self, count, xpath): + if not count: + return "No element matching '%s' found." % xpath + if count == 1: + return "One element matching '%s' found." % xpath + return "Multiple elements (%d) matching '%s' found." % (count, xpath) + + def get_elements(self, source, xpath): + """Returns a list of elements in the ``source`` matching the ``xpath``. + + The ``source`` can be a path to an XML file, a string containing XML, or + an already parsed XML element. The ``xpath`` specifies which element to + find. See the `introduction` for more details. + + Elements matching the ``xpath`` are returned as a list. If no elements + match, an empty list is returned. Use `Get Element` if you want to get + exactly one match. + + Examples using ``${XML}`` structure from `Example`: + | ${children} = | Get Elements | ${XML} | third/child | + | Length Should Be | ${children} | 2 | | + | ${children} = | Get Elements | ${XML} | first/child | + | Should Be Empty | ${children} | | | + """ + if is_string(source) or is_bytes(source): + source = self.parse_xml(source) + finder = ElementFinder(self.etree, self.modern_etree, self.lxml_etree) + return finder.find_all(source, xpath) + + def get_child_elements(self, source, xpath='.'): + """Returns the child elements of the specified element as a list. + + The element whose children to return is specified using ``source`` and + ``xpath``. They have exactly the same semantics as with `Get Element` + keyword. + + All the direct child elements of the specified element are returned. + If the element has no children, an empty list is returned. + + Examples using ``${XML}`` structure from `Example`: + | ${children} = | Get Child Elements | ${XML} | | + | Length Should Be | ${children} | 4 | | + | ${children} = | Get Child Elements | ${XML} | xpath=first | + | Should Be Empty | ${children} | | | + """ + return list(self.get_element(source, xpath)) + + def get_element_count(self, source, xpath='.'): + """Returns and logs how many elements the given ``xpath`` matches. + + Arguments ``source`` and ``xpath`` have exactly the same semantics as + with `Get Elements` keyword that this keyword uses internally. + + See also `Element Should Exist` and `Element Should Not Exist`. + """ + count = len(self.get_elements(source, xpath)) + logger.info("%d element%s matched '%s'." % (count, s(count), xpath)) + return count + + def element_should_exist(self, source, xpath='.', message=None): + """Verifies that one or more element match the given ``xpath``. + + Arguments ``source`` and ``xpath`` have exactly the same semantics as + with `Get Elements` keyword. Keyword passes if the ``xpath`` matches + one or more elements in the ``source``. The default error message can + be overridden with the ``message`` argument. + + See also `Element Should Not Exist` as well as `Get Element Count` + that this keyword uses internally. + """ + count = self.get_element_count(source, xpath) + if not count: + self._raise_wrong_number_of_matches(count, xpath, message) + + def element_should_not_exist(self, source, xpath='.', message=None): + """Verifies that no element match the given ``xpath``. + + Arguments ``source`` and ``xpath`` have exactly the same semantics as + with `Get Elements` keyword. Keyword fails if the ``xpath`` matches any + element in the ``source``. The default error message can be overridden + with the ``message`` argument. + + See also `Element Should Exist` as well as `Get Element Count` + that this keyword uses internally. + """ + count = self.get_element_count(source, xpath) + if count: + self._raise_wrong_number_of_matches(count, xpath, message) + + def get_element_text(self, source, xpath='.', normalize_whitespace=False): + """Returns all text of the element, possibly whitespace normalized. + + The element whose text to return is specified using ``source`` and + ``xpath``. They have exactly the same semantics as with `Get Element` + keyword. + + This keyword returns all the text of the specified element, including + all the text its children and grandchildren contain. If the element + has no text, an empty string is returned. The returned text is thus not + always the same as the `text` attribute of the element. + + By default all whitespace, including newlines and indentation, inside + the element is returned as-is. If ``normalize_whitespace`` is given + a true value (see `Boolean arguments`), then leading and trailing + whitespace is stripped, newlines and tabs converted to spaces, and + multiple spaces collapsed into one. This is especially useful when + dealing with HTML data. + + Examples using ``${XML}`` structure from `Example`: + | ${text} = | Get Element Text | ${XML} | first | + | Should Be Equal | ${text} | text | | + | ${text} = | Get Element Text | ${XML} | second/child | + | Should Be Empty | ${text} | | | + | ${paragraph} = | Get Element | ${XML} | html/p | + | ${text} = | Get Element Text | ${paragraph} | normalize_whitespace=yes | + | Should Be Equal | ${text} | Text with bold and italics. | + + See also `Get Elements Texts`, `Element Text Should Be` and + `Element Text Should Match`. + """ + element = self.get_element(source, xpath) + text = ''.join(self._yield_texts(element)) + if is_truthy(normalize_whitespace): + text = self._normalize_whitespace(text) + return text + + def _yield_texts(self, element, top=True): + if element.text: + yield element.text + for child in element: + for text in self._yield_texts(child, top=False): + yield text + if element.tail and not top: + yield element.tail + + def _normalize_whitespace(self, text): + return ' '.join(text.split()) + + def get_elements_texts(self, source, xpath, normalize_whitespace=False): + """Returns text of all elements matching ``xpath`` as a list. + + The elements whose text to return is specified using ``source`` and + ``xpath``. They have exactly the same semantics as with `Get Elements` + keyword. + + The text of the matched elements is returned using the same logic + as with `Get Element Text`. This includes optional whitespace + normalization using the ``normalize_whitespace`` option. + + Examples using ``${XML}`` structure from `Example`: + | @{texts} = | Get Elements Texts | ${XML} | third/child | + | Length Should Be | ${texts} | 2 | | + | Should Be Equal | @{texts}[0] | more text | | + | Should Be Equal | @{texts}[1] | ${EMPTY} | | + """ + return [self.get_element_text(elem, normalize_whitespace=normalize_whitespace) + for elem in self.get_elements(source, xpath)] + + def element_text_should_be(self, source, expected, xpath='.', + normalize_whitespace=False, message=None): + """Verifies that the text of the specified element is ``expected``. + + The element whose text is verified is specified using ``source`` and + ``xpath``. They have exactly the same semantics as with `Get Element` + keyword. + + The text to verify is got from the specified element using the same + logic as with `Get Element Text`. This includes optional whitespace + normalization using the ``normalize_whitespace`` option. + + The keyword passes if the text of the element is equal to the + ``expected`` value, and otherwise it fails. The default error message + can be overridden with the ``message`` argument. Use `Element Text + Should Match` to verify the text against a pattern instead of an exact + value. + + Examples using ``${XML}`` structure from `Example`: + | Element Text Should Be | ${XML} | text | xpath=first | + | Element Text Should Be | ${XML} | ${EMPTY} | xpath=second/child | + | ${paragraph} = | Get Element | ${XML} | xpath=html/p | + | Element Text Should Be | ${paragraph} | Text with bold and italics. | normalize_whitespace=yes | + """ + text = self.get_element_text(source, xpath, normalize_whitespace) + should_be_equal(text, expected, message, values=False) + + def element_text_should_match(self, source, pattern, xpath='.', + normalize_whitespace=False, message=None): + """Verifies that the text of the specified element matches ``expected``. + + This keyword works exactly like `Element Text Should Be` except that + the expected value can be given as a pattern that the text of the + element must match. + + Pattern matching is similar as matching files in a shell with + ``*``, ``?`` and ``[chars]`` acting as wildcards. See the + `Pattern matching` section for more information. + + Examples using ``${XML}`` structure from `Example`: + | Element Text Should Match | ${XML} | t??? | xpath=first | + | ${paragraph} = | Get Element | ${XML} | xpath=html/p | + | Element Text Should Match | ${paragraph} | Text with * and *. | normalize_whitespace=yes | + """ + text = self.get_element_text(source, xpath, normalize_whitespace) + should_match(text, pattern, message, values=False) + + def get_element_attribute(self, source, name, xpath='.', default=None): + """Returns the named attribute of the specified element. + + The element whose attribute to return is specified using ``source`` and + ``xpath``. They have exactly the same semantics as with `Get Element` + keyword. + + The value of the attribute ``name`` of the specified element is returned. + If the element does not have such element, the ``default`` value is + returned instead. + + Examples using ``${XML}`` structure from `Example`: + | ${attribute} = | Get Element Attribute | ${XML} | id | xpath=first | + | Should Be Equal | ${attribute} | 1 | | | + | ${attribute} = | Get Element Attribute | ${XML} | xx | xpath=first | default=value | + | Should Be Equal | ${attribute} | value | | | + + See also `Get Element Attributes`, `Element Attribute Should Be`, + `Element Attribute Should Match` and `Element Should Not Have Attribute`. + """ + return self.get_element(source, xpath).get(name, default) + + def get_element_attributes(self, source, xpath='.'): + """Returns all attributes of the specified element. + + The element whose attributes to return is specified using ``source`` and + ``xpath``. They have exactly the same semantics as with `Get Element` + keyword. + + Attributes are returned as a Python dictionary. It is a copy of the + original attributes so modifying it has no effect on the XML structure. + + Examples using ``${XML}`` structure from `Example`: + | ${attributes} = | Get Element Attributes | ${XML} | first | + | Dictionary Should Contain Key | ${attributes} | id | | + | ${attributes} = | Get Element Attributes | ${XML} | third | + | Should Be Empty | ${attributes} | | | + + Use `Get Element Attribute` to get the value of a single attribute. + """ + return dict(self.get_element(source, xpath).attrib) + + def element_attribute_should_be(self, source, name, expected, xpath='.', + message=None): + """Verifies that the specified attribute is ``expected``. + + The element whose attribute is verified is specified using ``source`` + and ``xpath``. They have exactly the same semantics as with + `Get Element` keyword. + + The keyword passes if the attribute ``name`` of the element is equal to + the ``expected`` value, and otherwise it fails. The default error + message can be overridden with the ``message`` argument. + + To test that the element does not have a certain attribute, Python + ``None`` (i.e. variable ``${NONE}``) can be used as the expected value. + A cleaner alternative is using `Element Should Not Have Attribute`. + + Examples using ``${XML}`` structure from `Example`: + | Element Attribute Should Be | ${XML} | id | 1 | xpath=first | + | Element Attribute Should Be | ${XML} | id | ${NONE} | | + + See also `Element Attribute Should Match` and `Get Element Attribute`. + """ + attr = self.get_element_attribute(source, name, xpath) + should_be_equal(attr, expected, message, values=False) + + def element_attribute_should_match(self, source, name, pattern, xpath='.', + message=None): + """Verifies that the specified attribute matches ``expected``. + + This keyword works exactly like `Element Attribute Should Be` except + that the expected value can be given as a pattern that the attribute of + the element must match. + + Pattern matching is similar as matching files in a shell with + ``*``, ``?`` and ``[chars]`` acting as wildcards. See the + `Pattern matching` section for more information. + + Examples using ``${XML}`` structure from `Example`: + | Element Attribute Should Match | ${XML} | id | ? | xpath=first | + | Element Attribute Should Match | ${XML} | id | c*d | xpath=third/second | + """ + attr = self.get_element_attribute(source, name, xpath) + if attr is None: + raise AssertionError("Attribute '%s' does not exist." % name) + should_match(attr, pattern, message, values=False) + + def element_should_not_have_attribute(self, source, name, xpath='.', message=None): + """Verifies that the specified element does not have attribute ``name``. + + The element whose attribute is verified is specified using ``source`` + and ``xpath``. They have exactly the same semantics as with + `Get Element` keyword. + + The keyword fails if the specified element has attribute ``name``. The + default error message can be overridden with the ``message`` argument. + + Examples using ``${XML}`` structure from `Example`: + | Element Should Not Have Attribute | ${XML} | id | + | Element Should Not Have Attribute | ${XML} | xxx | xpath=first | + + See also `Get Element Attribute`, `Get Element Attributes`, + `Element Text Should Be` and `Element Text Should Match`. + """ + attr = self.get_element_attribute(source, name, xpath) + if attr is not None: + raise AssertionError(message or "Attribute '%s' exists and " + "has value '%s'." % (name, attr)) + + def elements_should_be_equal(self, source, expected, exclude_children=False, + normalize_whitespace=False): + """Verifies that the given ``source`` element is equal to ``expected``. + + Both ``source`` and ``expected`` can be given as a path to an XML file, + as a string containing XML, or as an already parsed XML element + structure. See `introduction` for more information about parsing XML in + general. + + The keyword passes if the ``source`` element and ``expected`` element + are equal. This includes testing the tag names, texts, and attributes + of the elements. By default also child elements are verified the same + way, but this can be disabled by setting ``exclude_children`` to a + true value (see `Boolean arguments`). + + All texts inside the given elements are verified, but possible text + outside them is not. By default texts must match exactly, but setting + ``normalize_whitespace`` to a true value makes text verification + independent on newlines, tabs, and the amount of spaces. For more + details about handling text see `Get Element Text` keyword and + discussion about elements' `text` and `tail` attributes in the + `introduction`. + + Examples using ``${XML}`` structure from `Example`: + | ${first} = | Get Element | ${XML} | first | + | Elements Should Be Equal | ${first} | text | + | ${p} = | Get Element | ${XML} | html/p | + | Elements Should Be Equal | ${p} |

Text with bold and italics.

| normalize_whitespace=yes | + | Elements Should Be Equal | ${p} |

Text with

| exclude | normalize | + + The last example may look a bit strange because the ``

`` element + only has text ``Text with``. The reason is that rest of the text + inside ``

`` actually belongs to the child elements. This includes + the ``.`` at the end that is the `tail` text of the ```` element. + + See also `Elements Should Match`. + """ + self._compare_elements(source, expected, should_be_equal, + exclude_children, normalize_whitespace) + + def elements_should_match(self, source, expected, exclude_children=False, + normalize_whitespace=False): + """Verifies that the given ``source`` element matches ``expected``. + + This keyword works exactly like `Elements Should Be Equal` except that + texts and attribute values in the expected value can be given as + patterns. + + Pattern matching is similar as matching files in a shell with + ``*``, ``?`` and ``[chars]`` acting as wildcards. See the + `Pattern matching` section for more information. + + Examples using ``${XML}`` structure from `Example`: + | ${first} = | Get Element | ${XML} | first | + | Elements Should Match | ${first} | * | + + See `Elements Should Be Equal` for more examples. + """ + self._compare_elements(source, expected, should_match, + exclude_children, normalize_whitespace) + + def _compare_elements(self, source, expected, comparator, exclude_children, + normalize_whitespace): + normalizer = self._normalize_whitespace \ + if is_truthy(normalize_whitespace) else None + comparator = ElementComparator(comparator, normalizer, exclude_children) + comparator.compare(self.get_element(source), self.get_element(expected)) + + def set_element_tag(self, source, tag, xpath='.'): + """Sets the tag of the specified element. + + The element whose tag to set is specified using ``source`` and + ``xpath``. They have exactly the same semantics as with `Get Element` + keyword. The resulting XML structure is returned, and if the ``source`` + is an already parsed XML structure, it is also modified in place. + + Examples using ``${XML}`` structure from `Example`: + | Set Element Tag | ${XML} | newTag | + | Should Be Equal | ${XML.tag} | newTag | + | Set Element Tag | ${XML} | xxx | xpath=second/child | + | Element Should Exist | ${XML} | second/xxx | + | Element Should Not Exist | ${XML} | second/child | + + Can only set the tag of a single element. Use `Set Elements Tag` to set + the tag of multiple elements in one call. + """ + source = self.get_element(source) + self.get_element(source, xpath).tag = tag + return source + + def set_elements_tag(self, source, tag, xpath='.'): + """Sets the tag of the specified elements. + + Like `Set Element Tag` but sets the tag of all elements matching + the given ``xpath``. + """ + for elem in self.get_elements(source, xpath): + self.set_element_tag(elem, tag) + + def set_element_text(self, source, text=None, tail=None, xpath='.'): + """Sets text and/or tail text of the specified element. + + The element whose text to set is specified using ``source`` and + ``xpath``. They have exactly the same semantics as with `Get Element` + keyword. The resulting XML structure is returned, and if the ``source`` + is an already parsed XML structure, it is also modified in place. + + Element's text and tail text are changed only if new ``text`` and/or + ``tail`` values are given. See `Element attributes` section for more + information about `text` and `tail` in general. + + Examples using ``${XML}`` structure from `Example`: + | Set Element Text | ${XML} | new text | xpath=first | + | Element Text Should Be | ${XML} | new text | xpath=first | + | Set Element Text | ${XML} | tail=& | xpath=html/p/b | + | Element Text Should Be | ${XML} | Text with bold&italics. | xpath=html/p | normalize_whitespace=yes | + | Set Element Text | ${XML} | slanted | !! | xpath=html/p/i | + | Element Text Should Be | ${XML} | Text with bold&slanted!! | xpath=html/p | normalize_whitespace=yes | + + Can only set the text/tail of a single element. Use `Set Elements Text` + to set the text/tail of multiple elements in one call. + """ + source = self.get_element(source) + element = self.get_element(source, xpath) + if text is not None: + element.text = text + if tail is not None: + element.tail = tail + return source + + def set_elements_text(self, source, text=None, tail=None, xpath='.'): + """Sets text and/or tail text of the specified elements. + + Like `Set Element Text` but sets the text or tail of all elements + matching the given ``xpath``. + """ + for elem in self.get_elements(source, xpath): + self.set_element_text(elem, text, tail) + + def set_element_attribute(self, source, name, value, xpath='.'): + """Sets attribute ``name`` of the specified element to ``value``. + + The element whose attribute to set is specified using ``source`` and + ``xpath``. They have exactly the same semantics as with `Get Element` + keyword. The resulting XML structure is returned, and if the ``source`` + is an already parsed XML structure, it is also modified in place. + + It is possible to both set new attributes and to overwrite existing. + Use `Remove Element Attribute` or `Remove Element Attributes` for + removing them. + + Examples using ``${XML}`` structure from `Example`: + | Set Element Attribute | ${XML} | attr | value | + | Element Attribute Should Be | ${XML} | attr | value | + | Set Element Attribute | ${XML} | id | new | xpath=first | + | Element Attribute Should Be | ${XML} | id | new | xpath=first | + + Can only set an attribute of a single element. Use `Set Elements + Attribute` to set an attribute of multiple elements in one call. + """ + if not name: + raise RuntimeError('Attribute name can not be empty.') + source = self.get_element(source) + self.get_element(source, xpath).attrib[name] = value + return source + + def set_elements_attribute(self, source, name, value, xpath='.'): + """Sets attribute ``name`` of the specified elements to ``value``. + + Like `Set Element Attribute` but sets the attribute of all elements + matching the given ``xpath``. + """ + for elem in self.get_elements(source, xpath): + self.set_element_attribute(elem, name, value) + + def remove_element_attribute(self, source, name, xpath='.'): + """Removes attribute ``name`` from the specified element. + + The element whose attribute to remove is specified using ``source`` and + ``xpath``. They have exactly the same semantics as with `Get Element` + keyword. The resulting XML structure is returned, and if the ``source`` + is an already parsed XML structure, it is also modified in place. + + It is not a failure to remove a non-existing attribute. Use `Remove + Element Attributes` to remove all attributes and `Set Element Attribute` + to set them. + + Examples using ``${XML}`` structure from `Example`: + | Remove Element Attribute | ${XML} | id | xpath=first | + | Element Should Not Have Attribute | ${XML} | id | xpath=first | + + Can only remove an attribute from a single element. Use `Remove Elements + Attribute` to remove an attribute of multiple elements in one call. + """ + source = self.get_element(source) + attrib = self.get_element(source, xpath).attrib + if name in attrib: + attrib.pop(name) + return source + + def remove_elements_attribute(self, source, name, xpath='.'): + """Removes attribute ``name`` from the specified elements. + + Like `Remove Element Attribute` but removes the attribute of all + elements matching the given ``xpath``. + """ + for elem in self.get_elements(source, xpath): + self.remove_element_attribute(elem, name) + + def remove_element_attributes(self, source, xpath='.'): + """Removes all attributes from the specified element. + + The element whose attributes to remove is specified using ``source`` and + ``xpath``. They have exactly the same semantics as with `Get Element` + keyword. The resulting XML structure is returned, and if the ``source`` + is an already parsed XML structure, it is also modified in place. + + Use `Remove Element Attribute` to remove a single attribute and + `Set Element Attribute` to set them. + + Examples using ``${XML}`` structure from `Example`: + | Remove Element Attributes | ${XML} | xpath=first | + | Element Should Not Have Attribute | ${XML} | id | xpath=first | + + Can only remove attributes from a single element. Use `Remove Elements + Attributes` to remove all attributes of multiple elements in one call. + """ + source = self.get_element(source) + self.get_element(source, xpath).attrib.clear() + return source + + def remove_elements_attributes(self, source, xpath='.'): + """Removes all attributes from the specified elements. + + Like `Remove Element Attributes` but removes all attributes of all + elements matching the given ``xpath``. + """ + for elem in self.get_elements(source, xpath): + self.remove_element_attributes(elem) + + def add_element(self, source, element, index=None, xpath='.'): + """Adds a child element to the specified element. + + The element to whom to add the new element is specified using ``source`` + and ``xpath``. They have exactly the same semantics as with `Get Element` + keyword. The resulting XML structure is returned, and if the ``source`` + is an already parsed XML structure, it is also modified in place. + + The ``element`` to add can be specified as a path to an XML file or + as a string containing XML, or it can be an already parsed XML element. + The element is copied before adding so modifying either the original + or the added element has no effect on the other + . + The element is added as the last child by default, but a custom index + can be used to alter the position. Indices start from zero (0 = first + position, 1 = second position, etc.), and negative numbers refer to + positions at the end (-1 = second last position, -2 = third last, etc.). + + Examples using ``${XML}`` structure from `Example`: + | Add Element | ${XML} | | + | Add Element | ${XML} | | xpath=new | + | Add Element | ${XML} | | index=1 | xpath=new | + | ${new} = | Get Element | ${XML} | new | + | Elements Should Be Equal | ${new} | | + + Use `Remove Element` or `Remove Elements` to remove elements. + """ + source = self.get_element(source) + parent = self.get_element(source, xpath) + element = self.copy_element(element) + if index is None: + parent.append(element) + else: + parent.insert(int(index), element) + return source + + def remove_element(self, source, xpath='', remove_tail=False): + """Removes the element matching ``xpath`` from the ``source`` structure. + + The element to remove from the ``source`` is specified with ``xpath`` + using the same semantics as with `Get Element` keyword. The resulting + XML structure is returned, and if the ``source`` is an already parsed + XML structure, it is also modified in place. + + The keyword fails if ``xpath`` does not match exactly one element. + Use `Remove Elements` to remove all matched elements. + + Element's tail text is not removed by default, but that can be changed + by giving ``remove_tail`` a true value (see `Boolean arguments`). See + `Element attributes` section for more information about `tail` in + general. + + Examples using ``${XML}`` structure from `Example`: + | Remove Element | ${XML} | xpath=second | + | Element Should Not Exist | ${XML} | xpath=second | + | Remove Element | ${XML} | xpath=html/p/b | remove_tail=yes | + | Element Text Should Be | ${XML} | Text with italics. | xpath=html/p | normalize_whitespace=yes | + """ + source = self.get_element(source) + self._remove_element(source, self.get_element(source, xpath), remove_tail) + return source + + def remove_elements(self, source, xpath='', remove_tail=False): + """Removes all elements matching ``xpath`` from the ``source`` structure. + + The elements to remove from the ``source`` are specified with ``xpath`` + using the same semantics as with `Get Elements` keyword. The resulting + XML structure is returned, and if the ``source`` is an already parsed + XML structure, it is also modified in place. + + It is not a failure if ``xpath`` matches no elements. Use `Remove + Element` to remove exactly one element. + + Element's tail text is not removed by default, but that can be changed + by using ``remove_tail`` argument similarly as with `Remove Element`. + + Examples using ``${XML}`` structure from `Example`: + | Remove Elements | ${XML} | xpath=*/child | + | Element Should Not Exist | ${XML} | xpath=second/child | + | Element Should Not Exist | ${XML} | xpath=third/child | + """ + source = self.get_element(source) + for element in self.get_elements(source, xpath): + self._remove_element(source, element, remove_tail) + return source + + def _remove_element(self, root, element, remove_tail=False): + parent = self._find_parent(root, element) + if not is_truthy(remove_tail): + self._preserve_tail(element, parent) + parent.remove(element) + + def _find_parent(self, root, element): + all_elements = root.getiterator() if PY2 else root.iter() + for parent in all_elements: + for child in parent: + if child is element: + return parent + raise RuntimeError('Cannot remove root element.') + + def _preserve_tail(self, element, parent): + if not element.tail: + return + index = list(parent).index(element) + if index == 0: + parent.text = (parent.text or '') + element.tail + else: + sibling = parent[index-1] + sibling.tail = (sibling.tail or '') + element.tail + + def clear_element(self, source, xpath='.', clear_tail=False): + """Clears the contents of the specified element. + + The element to clear is specified using ``source`` and ``xpath``. They + have exactly the same semantics as with `Get Element` keyword. + The resulting XML structure is returned, and if the ``source`` is + an already parsed XML structure, it is also modified in place. + + Clearing the element means removing its text, attributes, and children. + Element's tail text is not removed by default, but that can be changed + by giving ``clear_tail`` a true value (see `Boolean arguments`). See + `Element attributes` section for more information about tail in + general. + + Examples using ``${XML}`` structure from `Example`: + | Clear Element | ${XML} | xpath=first | + | ${first} = | Get Element | ${XML} | xpath=first | + | Elements Should Be Equal | ${first} | | + | Clear Element | ${XML} | xpath=html/p/b | clear_tail=yes | + | Element Text Should Be | ${XML} | Text with italics. | xpath=html/p | normalize_whitespace=yes | + | Clear Element | ${XML} | + | Elements Should Be Equal | ${XML} | | + + Use `Remove Element` to remove the whole element. + """ + source = self.get_element(source) + element = self.get_element(source, xpath) + tail = element.tail + element.clear() + if not is_truthy(clear_tail): + element.tail = tail + return source + + def copy_element(self, source, xpath='.'): + """Returns a copy of the specified element. + + The element to copy is specified using ``source`` and ``xpath``. They + have exactly the same semantics as with `Get Element` keyword. + + If the copy or the original element is modified afterwards, the changes + have no effect on the other. + + Examples using ``${XML}`` structure from `Example`: + | ${elem} = | Get Element | ${XML} | xpath=first | + | ${copy1} = | Copy Element | ${elem} | + | ${copy2} = | Copy Element | ${XML} | xpath=first | + | Set Element Text | ${XML} | new text | xpath=first | + | Set Element Attribute | ${copy1} | id | new | + | Elements Should Be Equal | ${elem} | new text | + | Elements Should Be Equal | ${copy1} | text | + | Elements Should Be Equal | ${copy2} | text | + """ + return copy.deepcopy(self.get_element(source, xpath)) + + def element_to_string(self, source, xpath='.', encoding=None): + """Returns the string representation of the specified element. + + The element to convert to a string is specified using ``source`` and + ``xpath``. They have exactly the same semantics as with `Get Element` + keyword. + + By default the string is returned as Unicode. If ``encoding`` argument + is given any value, the string is returned as bytes in the specified + encoding. The resulting string never contains the XML declaration. + + See also `Log Element` and `Save XML`. + """ + source = self.get_element(source, xpath) + string = self.etree.tostring(source, encoding='UTF-8').decode('UTF-8') + string = self._xml_declaration.sub('', string).strip() + if encoding: + string = string.encode(encoding) + return string + + def log_element(self, source, level='INFO', xpath='.'): + """Logs the string representation of the specified element. + + The element specified with ``source`` and ``xpath`` is first converted + into a string using `Element To String` keyword internally. The + resulting string is then logged using the given ``level``. + + The logged string is also returned. + """ + string = self.element_to_string(source, xpath) + logger.write(string, level) + return string + + def save_xml(self, source, path, encoding='UTF-8'): + """Saves the given element to the specified file. + + The element to save is specified with ``source`` using the same + semantics as with `Get Element` keyword. + + The file where the element is saved is denoted with ``path`` and the + encoding to use with ``encoding``. The resulting file always contains + the XML declaration. + + The resulting XML file may not be exactly the same as the original: + - Comments and processing instructions are always stripped. + - Possible doctype and namespace prefixes are only preserved when + `using lxml`. + - Other small differences are possible depending on the ElementTree + or lxml version. + + Use `Element To String` if you just need a string representation of + the element. + """ + path = os.path.abspath(path.replace('/', os.sep)) + elem = self.get_element(source) + tree = self.etree.ElementTree(elem) + config = {'encoding': encoding} + if self.modern_etree: + config['xml_declaration'] = True + if self.lxml_etree: + elem = self._ns_stripper.unstrip(elem) + # https://bugs.launchpad.net/lxml/+bug/1660433 + if tree.docinfo.doctype: + config['doctype'] = tree.docinfo.doctype + tree = self.etree.ElementTree(elem) + with open(path, 'wb') as output: + if 'doctype' in config: + output.write(self.etree.tostring(tree, **config)) + else: + tree.write(output, **config) + logger.info('XML saved to %s.' % (path, path), + html=True) + + def evaluate_xpath(self, source, expression, context='.'): + """Evaluates the given xpath expression and returns results. + + The element in which context the expression is executed is specified + using ``source`` and ``context`` arguments. They have exactly the same + semantics as ``source`` and ``xpath`` arguments have with `Get Element` + keyword. + + The xpath expression to evaluate is given as ``expression`` argument. + The result of the evaluation is returned as-is. + + Examples using ``${XML}`` structure from `Example`: + | ${count} = | Evaluate Xpath | ${XML} | count(third/*) | + | Should Be Equal | ${count} | ${3} | + | ${text} = | Evaluate Xpath | ${XML} | string(descendant::second[last()]/@id) | + | Should Be Equal | ${text} | child | + | ${bold} = | Evaluate Xpath | ${XML} | boolean(preceding-sibling::*[1] = 'bold') | context=html/p/i | + | Should Be Equal | ${bold} | ${True} | + + This keyword works only if lxml mode is taken into use when `importing` + the library. + """ + if not self.lxml_etree: + raise RuntimeError("'Evaluate Xpath' keyword only works in lxml mode.") + return self.get_element(source, context).xpath(expression) + + +class NameSpaceStripper(object): + + def __init__(self, etree, lxml_etree=False): + self.etree = etree + self.lxml_tree = lxml_etree + + def strip(self, elem, preserve=True, current_ns=None, top=True): + if elem.tag.startswith('{') and '}' in elem.tag: + ns, elem.tag = elem.tag[1:].split('}', 1) + if preserve and ns != current_ns: + elem.attrib['xmlns'] = ns + current_ns = ns + elif current_ns: + elem.attrib['xmlns'] = '' + current_ns = None + for child in elem: + self.strip(child, preserve, current_ns, top=False) + if top and not preserve and self.lxml_tree: + self.etree.cleanup_namespaces(elem) + + def unstrip(self, elem, current_ns=None, copied=False): + if not copied: + elem = copy.deepcopy(elem) + ns = elem.attrib.pop('xmlns', current_ns) + if ns: + elem.tag = '{%s}%s' % (ns, elem.tag) + for child in elem: + self.unstrip(child, ns, copied=True) + return elem + + +class ElementFinder(object): + + def __init__(self, etree, modern=True, lxml=False): + self.etree = etree + self.modern = modern + self.lxml = lxml + + def find_all(self, elem, xpath): + xpath = self._get_xpath(xpath) + if xpath == '.': # ET < 1.3 does not support '.' alone. + return [elem] + if not self.lxml: + return elem.findall(xpath) + finder = self.etree.ETXPath(xpath) + return finder(elem) + + def _get_xpath(self, xpath): + if not xpath: + raise RuntimeError('No xpath given.') + if self.modern: + return xpath + try: + return str(xpath) + except UnicodeError: + if not xpath.replace('/', '').isalnum(): + logger.warn('XPATHs containing non-ASCII characters and ' + 'other than tag names do not always work with ' + 'Python versions prior to 2.7. Verify results ' + 'manually and consider upgrading to 2.7.') + return xpath + + +class ElementComparator(object): + + def __init__(self, comparator, normalizer=None, exclude_children=False): + self._comparator = comparator + self._normalizer = normalizer or (lambda text: text) + self._exclude_children = is_truthy(exclude_children) + + def compare(self, actual, expected, location=None): + if not location: + location = Location(actual.tag) + self._compare_tags(actual, expected, location) + self._compare_attributes(actual, expected, location) + self._compare_texts(actual, expected, location) + if location.is_not_root: + self._compare_tails(actual, expected, location) + if not self._exclude_children: + self._compare_children(actual, expected, location) + + def _compare_tags(self, actual, expected, location): + self._compare(actual.tag, expected.tag, 'Different tag name', location, + should_be_equal) + + def _compare(self, actual, expected, message, location, comparator=None): + if location.is_not_root: + message = "%s at '%s'" % (message, location.path) + if not comparator: + comparator = self._comparator + comparator(actual, expected, message) + + def _compare_attributes(self, actual, expected, location): + self._compare(sorted(actual.attrib), sorted(expected.attrib), + 'Different attribute names', location, should_be_equal) + for key in actual.attrib: + self._compare(actual.attrib[key], expected.attrib[key], + "Different value for attribute '%s'" % key, location) + + def _compare_texts(self, actual, expected, location): + self._compare(self._text(actual.text), self._text(expected.text), + 'Different text', location) + + def _text(self, text): + return self._normalizer(text or '') + + def _compare_tails(self, actual, expected, location): + self._compare(self._text(actual.tail), self._text(expected.tail), + 'Different tail text', location) + + def _compare_children(self, actual, expected, location): + self._compare(len(actual), len(expected), 'Different number of child elements', + location, should_be_equal) + for act, exp in zip(actual, expected): + self.compare(act, exp, location.child(act.tag)) + + +class Location(object): + + def __init__(self, path, is_root=True): + self.path = path + self.is_not_root = not is_root + self._children = {} + + def child(self, tag): + if tag not in self._children: + self._children[tag] = 1 + else: + self._children[tag] += 1 + tag += '[%d]' % self._children[tag] + return Location('%s/%s' % (self.path, tag), is_root=False) diff --git a/robot/lib/python3.8/site-packages/robot/libraries/__init__.py b/robot/lib/python3.8/site-packages/robot/libraries/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5ab5ed45d0258499b7a36f161050aaf4d245ac2a --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/libraries/__init__.py @@ -0,0 +1,31 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Package hosting Robot Framework standard test libraries. + +Libraries are mainly used externally in the test data, but they can be +also used by custom test libraries if there is a need. Especially +the :class:`~robot.libraries.BuiltIn.BuiltIn` library is often useful +when there is a need to interact with the framework. + +Because libraries are documented using Robot Framework's own documentation +syntax, the generated API docs are not that well formed. It is thus better +to find the generated library documentations, for example, via +the http://robotframework.org web site. +""" + +STDLIBS = frozenset(('BuiltIn', 'Collections', 'DateTime', 'Dialogs', 'Easter', + 'OperatingSystem', 'Process', 'Remote', 'Reserved', + 'Screenshot', 'String', 'Telnet', 'XML')) diff --git a/robot/lib/python3.8/site-packages/robot/libraries/dialogs_ipy.py b/robot/lib/python3.8/site-packages/robot/libraries/dialogs_ipy.py new file mode 100644 index 0000000000000000000000000000000000000000..39d1b9e2d3f40e5a191a8cdfdae068962b43a6ca --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/libraries/dialogs_ipy.py @@ -0,0 +1,221 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import wpf # Loads required .NET Assemblies behind the scenes + +from System.Windows import (GridLength, SizeToContent, TextWrapping, Thickness, + Window, WindowStartupLocation) +from System.Windows.Controls import (Button, ColumnDefinition, Grid, Label, ListBox, + PasswordBox, RowDefinition, TextBlock, TextBox, SelectionMode) + + +class _WpfDialog(Window): + _left_button = 'OK' + _right_button = 'Cancel' + + def __init__(self, message, value=None, **extra): + self._initialize_dialog() + self._create_body(message, value, **extra) + self._create_buttons() + self._bind_esc_to_close_dialog() + self._result = None + + def _initialize_dialog(self): + self.Title = 'Robot Framework' + self.SizeToContent = SizeToContent.WidthAndHeight + self.WindowStartupLocation = WindowStartupLocation.CenterScreen + self.MinWidth = 300 + self.MinHeight = 100 + self.MaxWidth = 640 + grid = Grid() + left_column = ColumnDefinition() + right_column = ColumnDefinition() + grid.ColumnDefinitions.Add(left_column) + grid.ColumnDefinitions.Add(right_column) + label_row = RowDefinition() + label_row.Height = GridLength.Auto + selection_row = RowDefinition() + selection_row.Height = GridLength.Auto + button_row = RowDefinition() + button_row.Height = GridLength(50) + grid.RowDefinitions.Add(label_row) + grid.RowDefinitions.Add(selection_row) + grid.RowDefinitions.Add(button_row) + self.Content = grid + + def _create_body(self, message, value, **extra): + _label = Label() + textblock = TextBlock() + textblock.Text = message + textblock.TextWrapping = TextWrapping.Wrap + _label.Content = textblock + _label.Margin = Thickness(10) + _label.SetValue(Grid.ColumnSpanProperty, 2) + _label.SetValue(Grid.RowProperty, 0) + self.Content.AddChild(_label) + selector = self._create_selector(value, **extra) + if selector: + self.Content.AddChild(selector) + selector.Focus() + + def _create_selector(self, value): + return None + + def _create_buttons(self): + self.left_button = self._create_button(self._left_button, + self._left_button_clicked) + self.left_button.SetValue(Grid.ColumnProperty, 0) + self.left_button.IsDefault = True + self.right_button = self._create_button(self._right_button, + self._right_button_clicked) + if self.right_button: + self.right_button.SetValue(Grid.ColumnProperty, 1) + self.Content.AddChild(self.right_button) + self.left_button.SetValue(Grid.ColumnProperty, 0) + self.Content.AddChild(self.left_button) + else: + self.left_button.SetValue(Grid.ColumnSpanProperty, 2) + self.Content.AddChild(self.left_button) + + def _create_button(self, content, callback): + if content: + button = Button() + button.Margin = Thickness(10) + button.MaxHeight = 50 + button.MaxWidth = 150 + button.SetValue(Grid.RowProperty, 2) + button.Content = content + button.Click += callback + return button + + def _bind_esc_to_close_dialog(self): + # There doesn't seem to be easy way to bind esc otherwise than having + # a cancel button that binds it automatically. We don't always have + # actual cancel button so need to create one and make it invisible. + # Cannot actually hide it because it won't work after that so we just + # make it so small it is not seen. + button = Button() + button.IsCancel = True + button.MaxHeight = 1 + button.MaxWidth = 1 + self.Content.AddChild(button) + + def _left_button_clicked(self, sender, event_args): + if self._validate_value(): + self._result = self._get_value() + self._close() + + def _validate_value(self): + return True + + def _get_value(self): + return None + + def _close(self): + self.Close() + + def _right_button_clicked(self, sender, event_args): + self._result = self._get_right_button_value() + self._close() + + def _get_right_button_value(self): + return None + + def show(self): + self.ShowDialog() + return self._result + + +class MessageDialog(_WpfDialog): + _right_button = None + + +class InputDialog(_WpfDialog): + + def __init__(self, message, default='', hidden=False): + _WpfDialog.__init__(self, message, default, hidden=hidden) + + def _create_selector(self, default, hidden): + if hidden: + self._entry = PasswordBox() + self._entry.Password = default if default else '' + else: + self._entry = TextBox() + self._entry.Text = default if default else '' + self._entry.SetValue(Grid.RowProperty, 1) + self._entry.SetValue(Grid.ColumnSpanProperty, 2) + self.Margin = Thickness(10) + self._entry.Height = 30 + self._entry.Width = 150 + self._entry.SelectAll() + return self._entry + + def _get_value(self): + try: + return self._entry.Text + except AttributeError: + return self._entry.Password + + +class SelectionDialog(_WpfDialog): + + def __init__(self, message, values): + _WpfDialog.__init__(self, message, values) + + def _create_selector(self, values): + self._listbox = ListBox() + self._listbox.SetValue(Grid.RowProperty, 1) + self._listbox.SetValue(Grid.ColumnSpanProperty, 2) + self._listbox.Margin = Thickness(10) + for item in values: + self._listbox.Items.Add(item) + return self._listbox + + def _validate_value(self): + return bool(self._listbox.SelectedItem) + + def _get_value(self): + return self._listbox.SelectedItem + + +class MultipleSelectionDialog(_WpfDialog): + + def __init__(self, message, values): + _WpfDialog.__init__(self, message, values) + + def _create_selector(self, values): + self._listbox = ListBox() + self._listbox.SelectionMode = SelectionMode.Multiple + self._listbox.SetValue(Grid.RowProperty, 1) + self._listbox.SetValue(Grid.ColumnSpanProperty, 2) + self._listbox.Margin = Thickness(10) + for item in values: + self._listbox.Items.Add(item) + return self._listbox + + def _get_value(self): + return sorted(self._listbox.SelectedItems, + key=list(self._listbox.Items).index) + + +class PassFailDialog(_WpfDialog): + _left_button = 'PASS' + _right_button = 'FAIL' + + def _get_value(self): + return True + + def _get_right_button_value(self): + return False diff --git a/robot/lib/python3.8/site-packages/robot/libraries/dialogs_jy.py b/robot/lib/python3.8/site-packages/robot/libraries/dialogs_jy.py new file mode 100644 index 0000000000000000000000000000000000000000..655d413813a2e10587b2bf00aa03ed645035edff --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/libraries/dialogs_jy.py @@ -0,0 +1,156 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import textwrap +import time + +from java.awt import Component +from java.awt.event import WindowAdapter +from javax.swing import (BoxLayout, JLabel, JOptionPane, JPanel, + JPasswordField, JTextField, JList, JScrollPane) +from javax.swing.JOptionPane import (DEFAULT_OPTION, OK_CANCEL_OPTION, + OK_OPTION, PLAIN_MESSAGE, + UNINITIALIZED_VALUE, YES_NO_OPTION) + +from robot.utils import html_escape + + +MAX_CHARS_PER_LINE = 120 + + +class _SwingDialog(object): + + def __init__(self, pane): + self._pane = pane + + def _create_panel(self, message, widget): + panel = JPanel() + panel.setLayout(BoxLayout(panel, BoxLayout.Y_AXIS)) + label = self._create_label(message) + label.setAlignmentX(Component.LEFT_ALIGNMENT) + panel.add(label) + widget.setAlignmentX(Component.LEFT_ALIGNMENT) + panel.add(widget) + return panel + + def _create_label(self, message): + # JLabel doesn't support multiline text, setting size, or wrapping. + # Need to handle all that ourselves. Feels like 2005... + wrapper = textwrap.TextWrapper(MAX_CHARS_PER_LINE, + drop_whitespace=False) + lines = [] + for line in html_escape(message, linkify=False).splitlines(): + if line: + lines.extend(wrapper.wrap(line)) + else: + lines.append('') + return JLabel('%s' % '
'.join(lines)) + + def show(self): + self._show_dialog(self._pane) + return self._get_value(self._pane) + + def _show_dialog(self, pane): + dialog = pane.createDialog(None, 'Robot Framework') + dialog.setModal(False) + dialog.setAlwaysOnTop(True) + dialog.addWindowFocusListener(pane.focus_listener) + dialog.show() + while dialog.isShowing(): + time.sleep(0.2) + dialog.dispose() + + def _get_value(self, pane): + value = pane.getInputValue() + return value if value != UNINITIALIZED_VALUE else None + + +class MessageDialog(_SwingDialog): + + def __init__(self, message): + pane = WrappedOptionPane(message, PLAIN_MESSAGE, DEFAULT_OPTION) + _SwingDialog.__init__(self, pane) + + +class InputDialog(_SwingDialog): + + def __init__(self, message, default, hidden=False): + self._input_field = JPasswordField() if hidden else JTextField() + self._input_field.setText(default) + self._input_field.selectAll() + panel = self._create_panel(message, self._input_field) + pane = WrappedOptionPane(panel, PLAIN_MESSAGE, OK_CANCEL_OPTION) + pane.set_focus_listener(self._input_field) + _SwingDialog.__init__(self, pane) + + def _get_value(self, pane): + if pane.getValue() != OK_OPTION: + return None + return self._input_field.getText() + + +class SelectionDialog(_SwingDialog): + + def __init__(self, message, options): + pane = WrappedOptionPane(message, PLAIN_MESSAGE, OK_CANCEL_OPTION) + pane.setWantsInput(True) + pane.setSelectionValues(options) + _SwingDialog.__init__(self, pane) + + +class MultipleSelectionDialog(_SwingDialog): + + def __init__(self, message, options): + self._selection_list = JList(options) + self._selection_list.setVisibleRowCount(8) + panel = self._create_panel(message, JScrollPane(self._selection_list)) + pane = WrappedOptionPane(panel, PLAIN_MESSAGE, OK_CANCEL_OPTION) + _SwingDialog.__init__(self, pane) + + def _get_value(self, pane): + if pane.getValue() != OK_OPTION: + return None + return list(self._selection_list.getSelectedValuesList()) + + +class PassFailDialog(_SwingDialog): + + def __init__(self, message): + pane = WrappedOptionPane(message, PLAIN_MESSAGE, YES_NO_OPTION, + None, ['PASS', 'FAIL'], 'PASS') + _SwingDialog.__init__(self, pane) + + def _get_value(self, pane): + value = pane.getValue() + return value == 'PASS' if value in ['PASS', 'FAIL'] else None + + +class WrappedOptionPane(JOptionPane): + focus_listener = None + + def getMaxCharactersPerLineCount(self): + return MAX_CHARS_PER_LINE + + def set_focus_listener(self, component): + self.focus_listener = WindowFocusListener(component) + + +class WindowFocusListener(WindowAdapter): + + def __init__(self, component): + self.component = component + + def windowGainedFocus(self, event): + self.component.requestFocusInWindow() diff --git a/robot/lib/python3.8/site-packages/robot/libraries/dialogs_py.py b/robot/lib/python3.8/site-packages/robot/libraries/dialogs_py.py new file mode 100644 index 0000000000000000000000000000000000000000..3c076c09a9c46bc1e4df626c4318b0305e14c0e2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/libraries/dialogs_py.py @@ -0,0 +1,197 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from threading import currentThread +import time + +try: + from Tkinter import (Button, Entry, Frame, Label, Listbox, TclError, + Toplevel, Tk, BOTH, END, LEFT, W) +except ImportError: + from tkinter import (Button, Entry, Frame, Label, Listbox, TclError, + Toplevel, Tk, BOTH, END, LEFT, W) + + +class _TkDialog(Toplevel): + _left_button = 'OK' + _right_button = 'Cancel' + + def __init__(self, message, value=None, **extra): + self._prevent_execution_with_timeouts() + self._parent = self._get_parent() + Toplevel.__init__(self, self._parent) + self._initialize_dialog() + self._create_body(message, value, **extra) + self._create_buttons() + self._result = None + + def _prevent_execution_with_timeouts(self): + if 'linux' not in sys.platform \ + and currentThread().getName() != 'MainThread': + raise RuntimeError('Dialogs library is not supported with ' + 'timeouts on Python on this platform.') + + def _get_parent(self): + parent = Tk() + parent.withdraw() + return parent + + def _initialize_dialog(self): + self.title('Robot Framework') + self.grab_set() + self.protocol("WM_DELETE_WINDOW", self._close) + self.bind("", self._close) + self.minsize(250, 80) + self.geometry("+%d+%d" % self._get_center_location()) + self._bring_to_front() + + def grab_set(self, timeout=30): + maxtime = time.time() + timeout + while time.time() < maxtime: + try: + # Fails at least on Linux if mouse is hold down. + return Toplevel.grab_set(self) + except TclError: + pass + raise RuntimeError('Failed to open dialog in %s seconds. One possible ' + 'reason is holding down mouse button.' % timeout) + + def _get_center_location(self): + x = (self.winfo_screenwidth() - self.winfo_reqwidth()) // 2 + y = (self.winfo_screenheight() - self.winfo_reqheight()) // 2 + return x, y + + def _bring_to_front(self): + self.lift() + self.attributes('-topmost', True) + self.after_idle(self.attributes, '-topmost', False) + + def _create_body(self, message, value, **extra): + frame = Frame(self) + Label(frame, text=message, anchor=W, justify=LEFT, wraplength=800).pack(fill=BOTH) + selector = self._create_selector(frame, value, **extra) + if selector: + selector.pack(fill=BOTH) + selector.focus_set() + frame.pack(padx=5, pady=5, expand=1, fill=BOTH) + + def _create_selector(self, frame, value): + return None + + def _create_buttons(self): + frame = Frame(self) + self._create_button(frame, self._left_button, + self._left_button_clicked) + self._create_button(frame, self._right_button, + self._right_button_clicked) + frame.pack() + + def _create_button(self, parent, label, callback): + if label: + button = Button(parent, text=label, width=10, command=callback) + button.pack(side=LEFT, padx=5, pady=5) + + def _left_button_clicked(self, event=None): + if self._validate_value(): + self._result = self._get_value() + self._close() + + def _validate_value(self): + return True + + def _get_value(self): + return None + + def _close(self, event=None): + # self.destroy() is not enough on Linux + self._parent.destroy() + + def _right_button_clicked(self, event=None): + self._result = self._get_right_button_value() + self._close() + + def _get_right_button_value(self): + return None + + def show(self): + self.wait_window(self) + return self._result + + +class MessageDialog(_TkDialog): + _right_button = None + + +class InputDialog(_TkDialog): + + def __init__(self, message, default='', hidden=False): + _TkDialog.__init__(self, message, default, hidden=hidden) + + def _create_selector(self, parent, default, hidden): + self._entry = Entry(parent, show='*' if hidden else '') + self._entry.insert(0, default) + self._entry.select_range(0, END) + return self._entry + + def _get_value(self): + return self._entry.get() + + +class SelectionDialog(_TkDialog): + + def __init__(self, message, values): + _TkDialog.__init__(self, message, values) + + def _create_selector(self, parent, values): + self._listbox = Listbox(parent) + for item in values: + self._listbox.insert(END, item) + self._listbox.config(width=0) + return self._listbox + + def _validate_value(self): + return bool(self._listbox.curselection()) + + def _get_value(self): + return self._listbox.get(self._listbox.curselection()) + + +class MultipleSelectionDialog(_TkDialog): + + def __init__(self, message, values): + _TkDialog.__init__(self, message, values) + + def _create_selector(self, parent, values): + self._listbox = Listbox(parent, selectmode='multiple') + for item in values: + self._listbox.insert(END, item) + self._listbox.config(width=0) + return self._listbox + + def _get_value(self): + selected_values = [self._listbox.get(i) for i in self._listbox.curselection()] + return selected_values + + +class PassFailDialog(_TkDialog): + _left_button = 'PASS' + _right_button = 'FAIL' + + def _get_value(self): + return True + + def _get_right_button_value(self): + return False diff --git a/robot/lib/python3.8/site-packages/robot/model/__init__.py b/robot/lib/python3.8/site-packages/robot/model/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c04977ef495fd2159bcd67717ef6cbed769ce46a --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/__init__.py @@ -0,0 +1,40 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Package with generic, reusable and extensible model classes. + +This package contains, for example, :class:`~robot.model.testsuite.TestSuite`, +:class:`~robot.model.testcase.TestCase`, :class:`~robot.model.keyword.Keyword` +and :class:`~robot.model.visitor.SuiteVisitor` base classes. +These classes are extended both by :mod:`execution ` +and :mod:`result ` related model objects and used also +elsewhere. + +This package is considered stable. +""" + +from .configurer import SuiteConfigurer +from .testsuite import TestSuite +from .testcase import TestCase +from .keyword import Keyword, Keywords +from .message import Message +from .modifier import ModelModifier +from .tags import Tags, TagPattern, TagPatterns +from .criticality import Criticality +from .namepatterns import SuiteNamePatterns, TestNamePatterns +from .visitor import SuiteVisitor +from .totalstatistics import TotalStatisticsBuilder +from .statistics import Statistics +from .itemlist import ItemList diff --git a/robot/lib/python3.8/site-packages/robot/model/configurer.py b/robot/lib/python3.8/site-packages/robot/model/configurer.py new file mode 100644 index 0000000000000000000000000000000000000000..23c1c2869dbb02f5840a769da946eff4d35d3af6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/configurer.py @@ -0,0 +1,89 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import seq2str +from robot.errors import DataError + +from .visitor import SuiteVisitor + + +class SuiteConfigurer(SuiteVisitor): + + def __init__(self, name=None, doc=None, metadata=None, set_tags=None, + include_tags=None, exclude_tags=None, include_suites=None, + include_tests=None, empty_suite_ok=False): + self.name = name + self.doc = doc + self.metadata = metadata + self.set_tags = set_tags or [] + self.include_tags = include_tags + self.exclude_tags = exclude_tags + self.include_suites = include_suites + self.include_tests = include_tests + self.empty_suite_ok = empty_suite_ok + + @property + def add_tags(self): + return [t for t in self.set_tags if not t.startswith('-')] + + @property + def remove_tags(self): + return [t[1:] for t in self.set_tags if t.startswith('-')] + + def visit_suite(self, suite): + self._set_suite_attributes(suite) + self._filter(suite) + suite.set_tags(self.add_tags, self.remove_tags) + + def _set_suite_attributes(self, suite): + if self.name: + suite.name = self.name + if self.doc: + suite.doc = self.doc + if self.metadata: + suite.metadata.update(self.metadata) + + def _filter(self, suite): + name = suite.name + suite.filter(self.include_suites, self.include_tests, + self.include_tags, self.exclude_tags) + if not (suite.test_count or self.empty_suite_ok): + self._raise_no_tests_error(name, suite.rpa) + + def _raise_no_tests_error(self, suite, rpa=False): + parts = ['tests' if not rpa else 'tasks', + self._get_test_selector_msgs(), + self._get_suite_selector_msg()] + raise DataError("Suite '%s' contains no %s." + % (suite, ' '.join(p for p in parts if p))) + + def _get_test_selector_msgs(self): + parts = [] + for explanation, selector in [('matching tags', self.include_tags), + ('not matching tags', self.exclude_tags), + ('matching name', self.include_tests)]: + if selector: + parts.append(self._format_selector_msg(explanation, selector)) + return seq2str(parts, quote='') + + def _format_selector_msg(self, explanation, selector): + if len(selector) == 1 and explanation[-1] == 's': + explanation = explanation[:-1] + return '%s %s' % (explanation, seq2str(selector, lastsep=' or ')) + + def _get_suite_selector_msg(self): + if not self.include_suites: + return '' + return self._format_selector_msg('in suites', self.include_suites) diff --git a/robot/lib/python3.8/site-packages/robot/model/criticality.py b/robot/lib/python3.8/site-packages/robot/model/criticality.py new file mode 100644 index 0000000000000000000000000000000000000000..70f39ba05c74b9180e26d04364c5ad59e7f74173 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/criticality.py @@ -0,0 +1,43 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import py2to3 + +from .tags import TagPatterns + + +@py2to3 +class Criticality(object): + + def __init__(self, critical_tags=None, non_critical_tags=None): + self.critical_tags = self._get_tag_patterns(critical_tags) + self.non_critical_tags = self._get_tag_patterns(non_critical_tags) + + def _get_tag_patterns(self, tags): + return TagPatterns(tags) if not isinstance(tags, TagPatterns) else tags + + def tag_is_critical(self, tag): + return self.critical_tags.match(tag) + + def tag_is_non_critical(self, tag): + return self.non_critical_tags.match(tag) + + def test_is_critical(self, test): + if self.critical_tags and not self.critical_tags.match(test.tags): + return False + return not self.non_critical_tags.match(test.tags) + + def __nonzero__(self): + return bool(self.critical_tags or self.non_critical_tags) diff --git a/robot/lib/python3.8/site-packages/robot/model/filter.py b/robot/lib/python3.8/site-packages/robot/model/filter.py new file mode 100644 index 0000000000000000000000000000000000000000..c074e2803689af6f000aed42b09b2215bf18d8f4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/filter.py @@ -0,0 +1,107 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import py2to3, setter + +from .tags import TagPatterns +from .namepatterns import SuiteNamePatterns, TestNamePatterns +from .visitor import SuiteVisitor + + +class EmptySuiteRemover(SuiteVisitor): + + def __init__(self, preserve_direct_children=False): + self.preserve_direct_children = preserve_direct_children + + def end_suite(self, suite): + if suite.parent or not self.preserve_direct_children: + suite.suites = [s for s in suite.suites if s.test_count] + + def visit_test(self, test): + pass + + def visit_keyword(self, kw): + pass + + +@py2to3 +class Filter(EmptySuiteRemover): + + def __init__(self, include_suites=None, include_tests=None, + include_tags=None, exclude_tags=None): + EmptySuiteRemover.__init__(self) + self.include_suites = include_suites + self.include_tests = include_tests + self.include_tags = include_tags + self.exclude_tags = exclude_tags + + @setter + def include_suites(self, suites): + return SuiteNamePatterns(suites) \ + if not isinstance(suites, SuiteNamePatterns) else suites + + @setter + def include_tests(self, tests): + return TestNamePatterns(tests) \ + if not isinstance(tests, TestNamePatterns) else tests + + @setter + def include_tags(self, tags): + return TagPatterns(tags) if not isinstance(tags, TagPatterns) else tags + + @setter + def exclude_tags(self, tags): + return TagPatterns(tags) if not isinstance(tags, TagPatterns) else tags + + def start_suite(self, suite): + if not self: + return False + if hasattr(suite, 'starttime'): + suite.starttime = suite.endtime = None + if self.include_suites: + return self._filter_by_suite_name(suite) + if self.include_tests: + suite.tests = self._filter(suite, self._included_by_test_name) + if self.include_tags: + suite.tests = self._filter(suite, self._included_by_tags) + if self.exclude_tags: + suite.tests = self._filter(suite, self._not_excluded_by_tags) + return bool(suite.suites) + + def _filter_by_suite_name(self, suite): + if self.include_suites.match(suite.name, suite.longname): + suite.visit(Filter(include_suites=[], + include_tests=self.include_tests, + include_tags=self.include_tags, + exclude_tags=self.exclude_tags)) + return False + suite.tests = [] + return True + + def _filter(self, suite, filter): + return [t for t in suite.tests if filter(t)] + + def _included_by_test_name(self, test): + return self.include_tests.match(test.name, test.longname) + + def _included_by_tags(self, test): + return self.include_tags.match(test.tags) + + def _not_excluded_by_tags(self, test): + return not self.exclude_tags.match(test.tags) + + def __nonzero__(self): + return bool(self.include_suites or self.include_tests or + self.include_tags or self.exclude_tags) diff --git a/robot/lib/python3.8/site-packages/robot/model/itemlist.py b/robot/lib/python3.8/site-packages/robot/model/itemlist.py new file mode 100644 index 0000000000000000000000000000000000000000..66b90b47594a9476d82886711df61eb21e1c4541 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/itemlist.py @@ -0,0 +1,173 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from functools import total_ordering + +from robot.utils import py2to3, unicode + + +# TODO: When Python 2 support is dropped, we could extend MutableSequence. +# In Python 2 it doesn't have slots: https://bugs.python.org/issue11333 + + +@total_ordering +@py2to3 +class ItemList(object): + __slots__ = ['_item_class', '_common_attrs', '_items'] + + def __init__(self, item_class, common_attrs=None, items=None): + self._item_class = item_class + self._common_attrs = common_attrs + self._items = [] + if items: + self.extend(items) + + def create(self, *args, **kwargs): + return self.append(self._item_class(*args, **kwargs)) + + def append(self, item): + self._check_type_and_set_attrs(item) + self._items.append(item) + return item + + def _check_type_and_set_attrs(self, *items): + common_attrs = self._common_attrs or {} + for item in items: + if not isinstance(item, self._item_class): + raise TypeError("Only %s objects accepted, got %s." + % (self._item_class.__name__, + item.__class__.__name__)) + for attr in common_attrs: + setattr(item, attr, common_attrs[attr]) + return items + + def extend(self, items): + self._items.extend(self._check_type_and_set_attrs(*items)) + + def insert(self, index, item): + self._check_type_and_set_attrs(item) + self._items.insert(index, item) + + def pop(self, *index): + return self._items.pop(*index) + + def remove(self, item): + self._items.remove(item) + + def index(self, item, *start_and_end): + return self._items.index(item, *start_and_end) + + def clear(self): + self._items = [] + + def visit(self, visitor): + for item in self: + item.visit(visitor) + + def __iter__(self): + index = 0 + while index < len(self._items): + yield self._items[index] + index += 1 + + def __getitem__(self, index): + if not isinstance(index, slice): + return self._items[index] + return self._create_new_from(self._items[index]) + + def _create_new_from(self, items): + # Cannot pass common_attrs directly to new object because all + # subclasses don't have compatible __init__. + new = type(self)(self._item_class) + new._common_attrs = self._common_attrs + new.extend(items) + return new + + def __setitem__(self, index, item): + if isinstance(index, slice): + self._check_type_and_set_attrs(*item) + else: + self._check_type_and_set_attrs(item) + self._items[index] = item + + def __delitem__(self, index): + del self._items[index] + + def __contains__(self, item): + return item in self._items + + def __len__(self): + return len(self._items) + + def __unicode__(self): + return u'[%s]' % ', '.join(unicode(item) for item in self) + + def count(self, item): + return self._items.count(item) + + def sort(self): + self._items.sort() + + def reverse(self): + self._items.reverse() + + def __reversed__(self): + index = 0 + while index < len(self._items): + yield self._items[len(self._items) - index - 1] + index += 1 + + def __eq__(self, other): + return (isinstance(other, ItemList) + and self._is_compatible(other) + and self._items == other._items) + + def _is_compatible(self, other): + return (self._item_class is other._item_class + and self._common_attrs == other._common_attrs) + + def __ne__(self, other): + # @total_ordering doesn't add __ne__ in Python < 2.7.15 + return not self == other + + def __lt__(self, other): + if not isinstance(other, ItemList): + raise TypeError('Cannot order ItemList and %s' % type(other).__name__) + if not self._is_compatible(other): + raise TypeError('Cannot order incompatible ItemLists') + return self._items < other._items + + def __add__(self, other): + if not isinstance(other, ItemList): + raise TypeError('Cannot add ItemList and %s' % type(other).__name__) + if not self._is_compatible(other): + raise TypeError('Cannot add incompatible ItemLists') + return self._create_new_from(self._items + other._items) + + def __iadd__(self, other): + if isinstance(other, ItemList) and not self._is_compatible(other): + raise TypeError('Cannot add incompatible ItemLists') + self.extend(other) + return self + + def __mul__(self, other): + return self._create_new_from(self._items * other) + + def __imul__(self, other): + self._items *= other + return self + + def __rmul__(self, other): + return self * other diff --git a/robot/lib/python3.8/site-packages/robot/model/keyword.py b/robot/lib/python3.8/site-packages/robot/model/keyword.py new file mode 100644 index 0000000000000000000000000000000000000000..6a223802f880ef932b61b55efc2a81946251a992 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/keyword.py @@ -0,0 +1,192 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from itertools import chain +from operator import attrgetter + +from robot.utils import setter + +from .itemlist import ItemList +from .message import Message, Messages +from .modelobject import ModelObject +from .tags import Tags + + +class Keyword(ModelObject): + """Base model for a single keyword. + + Extended by :class:`robot.running.model.Keyword` and + :class:`robot.result.model.Keyword`. + """ + __slots__ = ['_name', 'doc', 'args', 'assign', 'timeout', 'type', + '_sort_key', '_next_child_sort_key'] + KEYWORD_TYPE = 'kw' #: Normal keyword :attr:`type`. + SETUP_TYPE = 'setup' #: Setup :attr:`type`. + TEARDOWN_TYPE = 'teardown' #: Teardown :attr:`type`. + FOR_LOOP_TYPE = 'for' #: For loop :attr:`type`. + FOR_ITEM_TYPE = 'foritem' #: Single for loop iteration :attr:`type`. + keyword_class = None #: Internal usage only. + message_class = Message #: Internal usage only. + + def __init__(self, name='', doc='', args=(), assign=(), tags=(), + timeout=None, type=KEYWORD_TYPE): + self.parent = None + self._name = name + self.doc = doc + self.args = args #: Keyword arguments as a list of strings. + self.assign = assign #: Assigned variables as a list of strings. + self.tags = tags + self.timeout = timeout + #: Keyword type as a string. The value is either :attr:`KEYWORD_TYPE`, + #: :attr:`SETUP_TYPE`, :attr:`TEARDOWN_TYPE`, :attr:`FOR_LOOP_TYPE` or + #: :attr:`FOR_ITEM_TYPE` constant defined on the class level. + self.type = type + self.messages = None + self.keywords = None + self._sort_key = -1 + self._next_child_sort_key = 0 + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + self._name = name + + @setter + def parent(self, parent): + """Parent test suite, test case or keyword.""" + if parent and parent is not self.parent: + self._sort_key = getattr(parent, '_child_sort_key', -1) + return parent + + @property + def _child_sort_key(self): + self._next_child_sort_key += 1 + return self._next_child_sort_key + + @setter + def tags(self, tags): + """Keyword tags as a :class:`~.model.tags.Tags` object.""" + return Tags(tags) + + @setter + def keywords(self, keywords): + """Child keywords as a :class:`~.Keywords` object.""" + return Keywords(self.keyword_class or self.__class__, self, keywords) + + @setter + def messages(self, messages): + """Messages as a :class:`~.model.message.Messages` object.""" + return Messages(self.message_class, self, messages) + + @property + def children(self): + """Child :attr:`keywords` and :attr:`messages` in creation order.""" + # It would be cleaner to store keywords/messages in same `children` + # list and turn `keywords` and `messages` to properties that pick items + # from it. That would require bigger changes to the model, though. + return sorted(chain(self.keywords, self.messages), + key=attrgetter('_sort_key')) + + @property + def id(self): + """Keyword id in format like ``s1-t3-k1``. + + See :attr:`TestSuite.id ` for + more information. + """ + if not self.parent: + return 'k1' + return '%s-k%d' % (self.parent.id, self.parent.keywords.index(self)+1) + + @property + def source(self): + return self.parent.source if self.parent is not None else None + + def visit(self, visitor): + """:mod:`Visitor interface ` entry-point.""" + visitor.visit_keyword(self) + + +class Keywords(ItemList): + """A list-like object representing keywords in a suite, a test or a keyword. + + Possible setup and teardown keywords are directly available as + :attr:`setup` and :attr:`teardown` attributes. + """ + __slots__ = [] + + def __init__(self, keyword_class=Keyword, parent=None, keywords=None): + ItemList.__init__(self, keyword_class, {'parent': parent}, keywords) + + @property + def setup(self): + """Keyword used as the setup or ``None`` if no setup. + + Can be set to a new setup keyword or ``None`` since RF 3.0.1. + """ + return self[0] if (self and self[0].type == 'setup') else None + + @setup.setter + def setup(self, kw): + if kw is not None and kw.type != 'setup': + raise TypeError("Setup keyword type must be 'setup', " + "got '%s'." % kw.type) + if self.setup is not None: + self.pop(0) + if kw is not None: + self.insert(0, kw) + + def create_setup(self, *args, **kwargs): + self.setup = self._item_class(*args, type='setup', **kwargs) + + @property + def teardown(self): + """Keyword used as the teardown or ``None`` if no teardown. + + Can be set to a new teardown keyword or ``None`` since RF 3.0.1. + """ + return self[-1] if (self and self[-1].type == 'teardown') else None + + @teardown.setter + def teardown(self, kw): + if kw is not None and kw.type != 'teardown': + raise TypeError("Teardown keyword type must be 'teardown', " + "got '%s'." % kw.type) + if self.teardown is not None: + self.pop() + if kw is not None: + self.append(kw) + + def create_teardown(self, *args, **kwargs): + self.teardown = self._item_class(*args, type='teardown', **kwargs) + + @property + def all(self): + """Iterates over all keywords, including setup and teardown.""" + return self + + @property + def normal(self): + """Iterates over normal keywords, omitting setup and teardown.""" + kws = [kw for kw in self if kw.type not in ('setup', 'teardown')] + return Keywords(self._item_class, self._common_attrs['parent'], kws) + + def __setitem__(self, index, item): + old = self[index] + ItemList.__setitem__(self, index, item) + self[index]._sort_key = old._sort_key diff --git a/robot/lib/python3.8/site-packages/robot/model/message.py b/robot/lib/python3.8/site-packages/robot/model/message.py new file mode 100644 index 0000000000000000000000000000000000000000..d8be984cd3408272815ca02b0c3dcee1c70ddd26 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/message.py @@ -0,0 +1,75 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import html_escape, py2to3, setter + +from .itemlist import ItemList +from .modelobject import ModelObject + + +@py2to3 +class Message(ModelObject): + """A message created during the test execution. + + Can be a log message triggered by a keyword, or a warning or an error + that occurred during parsing or test execution. + """ + __slots__ = ['message', 'level', 'html', 'timestamp', '_sort_key'] + + def __init__(self, message='', level='INFO', html=False, timestamp=None, + parent=None): + #: The message content as a string. + self.message = message + #: Severity of the message. Either ``TRACE``, ``DEBUG``, ``INFO``, + #: ``WARN``, ``ERROR``, or ``FAIL``. The latest one is only used with + #: keyword failure messages. + self.level = level + #: ``True`` if the content is in HTML, ``False`` otherwise. + self.html = html + #: Timestamp in format ``%Y%m%d %H:%M:%S.%f``. + self.timestamp = timestamp + self._sort_key = -1 + #: The object this message was triggered by. + self.parent = parent + + @setter + def parent(self, parent): + if parent and parent is not getattr(self, 'parent', None): + self._sort_key = getattr(parent, '_child_sort_key', -1) + return parent + + @property + def html_message(self): + """Returns the message content as HTML.""" + return self.message if self.html else html_escape(self.message) + + def visit(self, visitor): + """:mod:`Visitor interface ` entry-point.""" + visitor.visit_message(self) + + def __unicode__(self): + return self.message + + +class Messages(ItemList): + __slots__ = [] + + def __init__(self, message_class=Message, parent=None, messages=None): + ItemList.__init__(self, message_class, {'parent': parent}, messages) + + def __setitem__(self, index, item): + old = self[index] + ItemList.__setitem__(self, index, item) + self[index]._sort_key = old._sort_key diff --git a/robot/lib/python3.8/site-packages/robot/model/metadata.py b/robot/lib/python3.8/site-packages/robot/model/metadata.py new file mode 100644 index 0000000000000000000000000000000000000000..b7e28d1ef8737b9e3cd868878b384bb8b3cdab45 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/metadata.py @@ -0,0 +1,33 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import is_string, NormalizedDict, py2to3, unic + + +@py2to3 +class Metadata(NormalizedDict): + + def __init__(self, initial=None): + NormalizedDict.__init__(self, initial, ignore='_') + + def __setitem__(self, key, value): + if not is_string(key): + key = unic(key) + if not is_string(value): + value = unic(value) + NormalizedDict.__setitem__(self, key, value) + + def __unicode__(self): + return u'{%s}' % ', '.join('%s: %s' % (k, self[k]) for k in self) diff --git a/robot/lib/python3.8/site-packages/robot/model/modelobject.py b/robot/lib/python3.8/site-packages/robot/model/modelobject.py new file mode 100644 index 0000000000000000000000000000000000000000..cd7bdf05e2823267e79e03651e73398bd6c021a0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/modelobject.py @@ -0,0 +1,85 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy + +from robot.utils import SetterAwareType, py2to3, unicode, with_metaclass + + +@py2to3 +class ModelObject(with_metaclass(SetterAwareType, object)): + __slots__ = [] + + def copy(self, **attributes): + """Return shallow copy of this object. + + :param attributes: Attributes to be set for the returned copy + automatically. For example, ``test.copy(name='New name')``. + + See also :meth:`deepcopy`. The difference between these two is the same + as with the standard ``copy.copy`` and ``copy.deepcopy`` functions + that these methods also use internally. + + New in Robot Framework 3.0.1. + """ + copied = copy.copy(self) + for name in attributes: + setattr(copied, name, attributes[name]) + return copied + + def deepcopy(self, **attributes): + """Return deep copy of this object. + + :param attributes: Attributes to be set for the returned copy + automatically. For example, ``test.deepcopy(name='New name')``. + + See also :meth:`copy`. The difference between these two is the same + as with the standard ``copy.copy`` and ``copy.deepcopy`` functions + that these methods also use internally. + + New in Robot Framework 3.0.1. + """ + copied = copy.deepcopy(self) + for name in attributes: + setattr(copied, name, attributes[name]) + return copied + + def __unicode__(self): + return self.name + + def __repr__(self): + return repr(unicode(self)) + + def __setstate__(self, state): + """Customize attribute updating when using the `copy` module. + + This may not be needed in the future if we fix the mess we have with + different timeout types. + """ + # We have __slots__ so state is always a two-tuple. + # Refer to: https://www.python.org/dev/peps/pep-0307 + dictstate, slotstate = state + if dictstate is not None: + self.__dict__.update(dictstate) + for name in slotstate: + # If attribute is defined in __slots__ and overridden by @setter + # (this is the case at least with 'timeout' of 'running.TestCase') + # we must not set the "real" attribute value because that would run + # the setter method and that would recreate the object when it + # should not. With timeouts recreating object using the object + # itself would also totally fail. + setter_name = '_setter__' + name + if setter_name not in slotstate: + setattr(self, name, slotstate[name]) diff --git a/robot/lib/python3.8/site-packages/robot/model/modifier.py b/robot/lib/python3.8/site-packages/robot/model/modifier.py new file mode 100644 index 0000000000000000000000000000000000000000..9ee8bf5e57ebdb32e4435474e7d3b44c2888e8f7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/modifier.py @@ -0,0 +1,52 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.utils import (get_error_details, is_string, + split_args_from_name_or_path, type_name, Importer) + +from .visitor import SuiteVisitor + + +class ModelModifier(SuiteVisitor): + + def __init__(self, visitors, empty_suite_ok, logger): + self._log_error = logger.error + self._empty_suite_ok = empty_suite_ok + self._visitors = list(self._yield_visitors(visitors)) + + def visit_suite(self, suite): + for visitor in self._visitors: + try: + suite.visit(visitor) + except: + message, details = get_error_details() + self._log_error("Executing model modifier '%s' failed: %s\n%s" + % (type_name(visitor), message, details)) + if not (suite.test_count or self._empty_suite_ok): + raise DataError("Suite '%s' contains no tests after model " + "modifiers." % suite.name) + + def _yield_visitors(self, visitors): + importer = Importer('model modifier') + for visitor in visitors: + try: + if not is_string(visitor): + yield visitor + else: + name, args = split_args_from_name_or_path(visitor) + yield importer.import_class_or_module(name, args) + except DataError as err: + self._log_error(err.message) diff --git a/robot/lib/python3.8/site-packages/robot/model/namepatterns.py b/robot/lib/python3.8/site-packages/robot/model/namepatterns.py new file mode 100644 index 0000000000000000000000000000000000000000..ae086893880cc386bcb61c8a9b0ce53defbb2db2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/namepatterns.py @@ -0,0 +1,54 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import MultiMatcher, py2to3 + + +@py2to3 +class _NamePatterns(object): + + def __init__(self, patterns=None): + self._matcher = MultiMatcher(patterns, ignore='_') + + def match(self, name, longname=None): + return self._match(name) or longname and self._match_longname(longname) + + def _match(self, name): + return self._matcher.match(name) + + def _match_longname(self, name): + raise NotImplementedError + + def __nonzero__(self): + return bool(self._matcher) + + def __iter__(self): + return iter(self._matcher) + + +class SuiteNamePatterns(_NamePatterns): + + def _match_longname(self, name): + while '.' in name: + if self._match(name): + return True + name = name.split('.', 1)[1] + return False + + +class TestNamePatterns(_NamePatterns): + + def _match_longname(self, name): + return self._match(name) diff --git a/robot/lib/python3.8/site-packages/robot/model/statistics.py b/robot/lib/python3.8/site-packages/robot/model/statistics.py new file mode 100644 index 0000000000000000000000000000000000000000..7d20d3f49294bb02babb81afc1f46b020e77956b --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/statistics.py @@ -0,0 +1,67 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .totalstatistics import TotalStatisticsBuilder +from .suitestatistics import SuiteStatisticsBuilder +from .tagstatistics import TagStatisticsBuilder +from .visitor import SuiteVisitor + + +class Statistics(object): + """Container for total, suite and tag statistics. + + Accepted parameters have the same semantics as the matching command line + options. + """ + def __init__(self, suite, suite_stat_level=-1, tag_stat_include=None, + tag_stat_exclude=None, tag_stat_combine=None, tag_doc=None, + tag_stat_link=None, rpa=False): + total_builder = TotalStatisticsBuilder(rpa=rpa) + suite_builder = SuiteStatisticsBuilder(suite_stat_level) + tag_builder = TagStatisticsBuilder(suite.criticality, tag_stat_include, + tag_stat_exclude, tag_stat_combine, + tag_doc, tag_stat_link) + suite.visit(StatisticsBuilder(total_builder, suite_builder, tag_builder)) + #: Instance of :class:`~robot.model.totalstatistics.TotalStatistics`. + self.total = total_builder.stats + #: Instance of :class:`~robot.model.suitestatistics.SuiteStatistics`. + self.suite = suite_builder.stats + #: Instance of :class:`~robot.model.tagstatistics.TagStatistics`. + self.tags = tag_builder.stats + + def visit(self, visitor): + visitor.visit_statistics(self) + + +class StatisticsBuilder(SuiteVisitor): + + def __init__(self, total_builder, suite_builder, tag_builder): + self._total_builder = total_builder + self._suite_builder = suite_builder + self._tag_builder = tag_builder + + def start_suite(self, suite): + self._suite_builder.start_suite(suite) + + def end_suite(self, suite): + self._suite_builder.end_suite() + + def visit_test(self, test): + self._total_builder.add_test(test) + self._suite_builder.add_test(test) + self._tag_builder.add_test(test) + + def visit_keyword(self, kw): + pass diff --git a/robot/lib/python3.8/site-packages/robot/model/stats.py b/robot/lib/python3.8/site-packages/robot/model/stats.py new file mode 100644 index 0000000000000000000000000000000000000000..7a4fc9200a01a430744c0c8940e32d86d7a652e8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/stats.py @@ -0,0 +1,192 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import (Sortable, elapsed_time_to_string, html_escape, + is_string, normalize, py2to3, unicode) + +from .tags import TagPattern + + +@py2to3 +class Stat(Sortable): + """Generic statistic object used for storing all the statistic values.""" + + def __init__(self, name): + #: Human readable identifier of the object these statistics + #: belong to. Either `All Tests` or `Critical Tests` for + #: :class:`~robot.model.totalstatistics.TotalStatistics`, + #: long name of the suite for + #: :class:`~robot.model.suitestatistics.SuiteStatistics` + #: or name of the tag for + #: :class:`~robot.model.tagstatistics.TagStatistics` + self.name = name + #: Number of passed tests. + self.passed = 0 + #: Number of failed tests. + self.failed = 0 + #: Number of milliseconds it took to execute. + self.elapsed = 0 + self._norm_name = normalize(name, ignore='_') + + def get_attributes(self, include_label=False, include_elapsed=False, + exclude_empty=True, values_as_strings=False, + html_escape=False): + attrs = {'pass': self.passed, 'fail': self.failed} + attrs.update(self._get_custom_attrs()) + if include_label: + attrs['label'] = self.name + if include_elapsed: + attrs['elapsed'] = elapsed_time_to_string(self.elapsed, + include_millis=False) + if exclude_empty: + attrs = dict((k, v) for k, v in attrs.items() if v not in ('', None)) + if values_as_strings: + attrs = dict((k, unicode(v if v is not None else '')) + for k, v in attrs.items()) + if html_escape: + attrs = dict((k, self._html_escape(v)) for k, v in attrs.items()) + return attrs + + def _get_custom_attrs(self): + return {} + + def _html_escape(self, item): + return html_escape(item) if is_string(item) else item + + @property + def total(self): + return self.passed + self.failed + + def add_test(self, test): + self._update_stats(test) + self._update_elapsed(test) + + def _update_stats(self, test): + if test.passed: + self.passed += 1 + else: + self.failed += 1 + + def _update_elapsed(self, test): + self.elapsed += test.elapsedtime + + @property + def _sort_key(self): + return self._norm_name + + def __nonzero__(self): + return not self.failed + + def visit(self, visitor): + visitor.visit_stat(self) + + +class TotalStat(Stat): + """Stores statistic values for a test run.""" + type = 'total' + + +class SuiteStat(Stat): + """Stores statistics values for a single suite.""" + type = 'suite' + + def __init__(self, suite): + Stat.__init__(self, suite.longname) + #: Identifier of the suite, e.g. `s1-s2`. + self.id = suite.id + #: Number of milliseconds it took to execute this suite, + #: including sub-suites. + self.elapsed = suite.elapsedtime + self._name = suite.name + + def _get_custom_attrs(self): + return {'id': self.id, 'name': self._name} + + def _update_elapsed(self, test): + pass + + def add_stat(self, other): + self.passed += other.passed + self.failed += other.failed + + +class TagStat(Stat): + """Stores statistic values for a single tag.""" + type = 'tag' + + def __init__(self, name, doc='', links=None, critical=False, + non_critical=False, combined=None): + Stat.__init__(self, name) + #: Documentation of tag as a string. + self.doc = doc + #: List of tuples in which the first value is the link URL and + #: the second is the link title. An empty list by default. + self.links = links or [] + #: ``True`` if tag is considered critical, ``False`` otherwise. + self.critical = critical + #: ``True`` if tag is considered non-critical, ``False`` otherwise. + self.non_critical = non_critical + #: Pattern as a string if the tag is combined, ``None`` otherwise. + self.combined = combined + + @property + def info(self): + """Returns additional information of the tag statistics + are about. Either `critical`, `non-critical`, `combined` or an + empty string. + """ + if self.critical: + return 'critical' + if self.non_critical: + return 'non-critical' + if self.combined: + return 'combined' + return '' + + def _get_custom_attrs(self): + return {'doc': self.doc, 'links': self._get_links_as_string(), + 'info': self.info, 'combined': self.combined} + + def _get_links_as_string(self): + return ':::'.join('%s:%s' % (title, url) for url, title in self.links) + + @property + def _sort_key(self): + return (not self.critical, + not self.non_critical, + not self.combined, + self._norm_name) + + +class CombinedTagStat(TagStat): + + def __init__(self, pattern, name=None, doc='', links=None): + TagStat.__init__(self, name or pattern, doc, links, combined=pattern) + self.pattern = TagPattern(pattern) + + def match(self, tags): + return self.pattern.match(tags) + + +class CriticalTagStat(TagStat): + + def __init__(self, tag_pattern, name=None, critical=True, doc='', + links=None): + TagStat.__init__(self, name or unicode(tag_pattern), doc, links, + critical=critical, non_critical=not critical) + self.pattern = tag_pattern + + def match(self, tags): + return self.pattern.match(tags) diff --git a/robot/lib/python3.8/site-packages/robot/model/suitestatistics.py b/robot/lib/python3.8/site-packages/robot/model/suitestatistics.py new file mode 100644 index 0000000000000000000000000000000000000000..64618982c06fce16b0fc3e43a569e5711c771e7e --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/suitestatistics.py @@ -0,0 +1,71 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .stats import SuiteStat + + +class SuiteStatistics(object): + """Container for suite statistics.""" + + def __init__(self, suite): + #: Instance of :class:`~robot.model.stats.SuiteStat`. + self.stat = SuiteStat(suite) + #: List of :class:`~robot.model.testsuite.TestSuite` objects. + self.suites = [] + + def visit(self, visitor): + visitor.visit_suite_statistics(self) + + def __iter__(self): + yield self.stat + for child in self.suites: + for stat in child: + yield stat + + +class SuiteStatisticsBuilder(object): + + def __init__(self, suite_stat_level): + self._suite_stat_level = suite_stat_level + self._stats_stack = [] + self.stats = None + + @property + def current(self): + return self._stats_stack[-1] if self._stats_stack else None + + def start_suite(self, suite): + self._stats_stack.append(SuiteStatistics(suite)) + if self.stats is None: + self.stats = self.current + + def add_test(self, test): + self.current.stat.add_test(test) + + def end_suite(self): + stats = self._stats_stack.pop() + if self.current: + self.current.stat.add_stat(stats.stat) + if self._is_child_included(): + self.current.suites.append(stats) + + def _is_child_included(self): + return self._include_all_levels() or self._below_threshold() + + def _include_all_levels(self): + return self._suite_stat_level == -1 + + def _below_threshold(self): + return len(self._stats_stack) < self._suite_stat_level diff --git a/robot/lib/python3.8/site-packages/robot/model/tags.py b/robot/lib/python3.8/site-packages/robot/model/tags.py new file mode 100644 index 0000000000000000000000000000000000000000..b898814665c60d95681316f28c593007712ae738 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/tags.py @@ -0,0 +1,191 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import (Matcher, normalize, NormalizedDict, is_string, py2to3, + setter, unic) + + +@py2to3 +class Tags(object): + + def __init__(self, tags=None): + self._tags = tags + + @setter + def _tags(self, tags): + if not tags: + return () + if is_string(tags): + tags = (tags,) + return self._deduplicate_normalized(tags) + + def _deduplicate_normalized(self, tags): + normalized = NormalizedDict(((unic(t), 1) for t in tags), ignore='_') + for removed in '', 'NONE': + if removed in normalized: + normalized.pop(removed) + return tuple(normalized) + + def add(self, tags): + self._tags = tuple(self) + tuple(Tags(tags)) + + def remove(self, tags): + tags = TagPatterns(tags) + self._tags = [t for t in self if not tags.match(t)] + + def match(self, tags): + return TagPatterns(tags).match(self) + + def __contains__(self, tags): + return self.match(tags) + + def __len__(self): + return len(self._tags) + + def __iter__(self): + return iter(self._tags) + + def __unicode__(self): + return u'[%s]' % ', '.join(self) + + def __repr__(self): + return repr(list(self)) + + def __eq__(self, other): + if not isinstance(other, Tags): + return False + self_normalized = [normalize(tag, ignore='_') for tag in self] + other_normalized = [normalize(tag, ignore='_') for tag in other] + return sorted(self_normalized) == sorted(other_normalized) + + def __ne__(self, other): + # Not necessary for Python3 (https://stackoverflow.com/a/30676267/2309247) + return not self == other + + def __getitem__(self, index): + item = self._tags[index] + return item if not isinstance(index, slice) else Tags(item) + + def __add__(self, other): + return Tags(tuple(self) + tuple(Tags(other))) + + +@py2to3 +class TagPatterns(object): + + def __init__(self, patterns): + self._patterns = tuple(TagPattern(p) for p in Tags(patterns)) + + def match(self, tags): + tags = tags if isinstance(tags, Tags) else Tags(tags) + return any(p.match(tags) for p in self._patterns) + + def __contains__(self, tag): + return self.match(tag) + + def __len__(self): + return len(self._patterns) + + def __iter__(self): + return iter(self._patterns) + + def __getitem__(self, index): + return self._patterns[index] + + def __unicode__(self): + return u'[%s]' % u', '.join(pattern.__unicode__() for pattern in self) + + +def TagPattern(pattern): + pattern = pattern.replace(' ', '') + if 'NOT' in pattern: + return NotTagPattern(*pattern.split('NOT')) + if 'OR' in pattern: + return OrTagPattern(pattern.split('OR')) + if 'AND' in pattern or '&' in pattern: + return AndTagPattern(pattern.replace('&', 'AND').split('AND')) + return SingleTagPattern(pattern) + + +@py2to3 +class SingleTagPattern(object): + + def __init__(self, pattern): + self._matcher = Matcher(pattern, ignore='_') + + def match(self, tags): + return self._matcher.match_any(tags) + + def __iter__(self): + yield self + + def __unicode__(self): + return self._matcher.pattern + + def __nonzero__(self): + return bool(self._matcher) + + +@py2to3 +class AndTagPattern(object): + + def __init__(self, patterns): + self._patterns = tuple(TagPattern(p) for p in patterns) + + def match(self, tags): + return all(p.match(tags) for p in self._patterns) + + def __iter__(self): + return iter(self._patterns) + + def __unicode__(self): + return ' AND '.join(pattern.__unicode__() for pattern in self) + + +@py2to3 +class OrTagPattern(object): + + def __init__(self, patterns): + self._patterns = tuple(TagPattern(p) for p in patterns) + + def match(self, tags): + return any(p.match(tags) for p in self._patterns) + + def __iter__(self): + return iter(self._patterns) + + def __unicode__(self): + return ' OR '.join(pattern.__unicode__() for pattern in self) + + +@py2to3 +class NotTagPattern(object): + + def __init__(self, must_match, *must_not_match): + self._first = TagPattern(must_match) + self._rest = OrTagPattern(must_not_match) + + def match(self, tags): + if not self._first: + return not self._rest.match(tags) + return self._first.match(tags) and not self._rest.match(tags) + + def __iter__(self): + yield self._first + for pattern in self._rest: + yield pattern + + def __unicode__(self): + return ' NOT '.join(pattern.__unicode__() for pattern in self).lstrip() diff --git a/robot/lib/python3.8/site-packages/robot/model/tagsetter.py b/robot/lib/python3.8/site-packages/robot/model/tagsetter.py new file mode 100644 index 0000000000000000000000000000000000000000..820631c022a449274dbf40d1504c04d259677f0e --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/tagsetter.py @@ -0,0 +1,39 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import py2to3 + +from .visitor import SuiteVisitor + + +@py2to3 +class TagSetter(SuiteVisitor): + + def __init__(self, add=None, remove=None): + self.add = add + self.remove = remove + + def start_suite(self, suite): + return bool(self) + + def visit_test(self, test): + test.tags.add(self.add) + test.tags.remove(self.remove) + + def visit_keyword(self, keyword): + pass + + def __nonzero__(self): + return bool(self.add or self.remove) diff --git a/robot/lib/python3.8/site-packages/robot/model/tagstatistics.py b/robot/lib/python3.8/site-packages/robot/model/tagstatistics.py new file mode 100644 index 0000000000000000000000000000000000000000..71d6f2de282791c634287da7137f0029d77ec314 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/tagstatistics.py @@ -0,0 +1,173 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from itertools import chain +import re + +from robot.utils import NormalizedDict, unicode + +from .stats import CombinedTagStat, CriticalTagStat, TagStat +from .tags import SingleTagPattern, TagPatterns + + +class TagStatistics(object): + """Container for tag statistics.""" + + def __init__(self, critical_stats, non_critical_stats, combined_stats): + #: Dictionary, where key is the name of the tag as a string and value + #: is an instance of :class:`~robot.model.stats.TagStat`. + self.tags = NormalizedDict(ignore='_') + #: List of :class:`~robot.model.stats.CriticalTagStat` objects. + self.critical = critical_stats + #: List of :class:`~robot.model.stats.CriticalTagStat` objects. + self.non_critical = non_critical_stats + #: List of :class:`~robot.model.stats.CombinedTagStat` objects. + self.combined = combined_stats + + def visit(self, visitor): + visitor.visit_tag_statistics(self) + + def __iter__(self): + crits = self._get_critical_and_non_critical_matcher() + tags = [t for t in self.tags.values() if t.name not in crits] + return iter(sorted(chain(self.critical, self.non_critical, + self.combined, tags))) + + def _get_critical_and_non_critical_matcher(self): + crits = [stat for stat in self.critical + self.non_critical + if isinstance(stat.pattern, SingleTagPattern)] + return NormalizedDict([(unicode(stat.pattern), None) for stat in crits], + ignore='_') + + +class TagStatisticsBuilder(object): + + def __init__(self, criticality=None, included=None, excluded=None, + combined=None, docs=None, links=None): + self._included = TagPatterns(included) + self._excluded = TagPatterns(excluded) + self._info = TagStatInfo(docs, links) + self.stats = TagStatistics( + self._info.get_critical_stats(criticality), + self._info.get_critical_stats(criticality, critical=False), + self._info.get_combined_stats(combined) + ) + + def add_test(self, test): + self._add_tags_to_statistics(test) + self._add_to_critical_and_combined_statistics(test) + + def _add_tags_to_statistics(self, test): + for tag in test.tags: + if self._is_included(tag): + if tag not in self.stats.tags: + self.stats.tags[tag] = self._info.get_stat(tag) + self.stats.tags[tag].add_test(test) + + def _is_included(self, tag): + if self._included and not self._included.match(tag): + return False + return not self._excluded.match(tag) + + def _add_to_critical_and_combined_statistics(self, test): + stats = self.stats + for stat in stats.critical + stats.non_critical + stats.combined: + if stat.match(test.tags): + stat.add_test(test) + + +class TagStatInfo(object): + + def __init__(self, docs=None, links=None): + self._docs = [TagStatDoc(*doc) for doc in docs or []] + self._links = [TagStatLink(*link) for link in links or []] + + def get_stat(self, tag): + return TagStat(tag, self.get_doc(tag), self.get_links(tag)) + + def get_critical_stats(self, criticality, critical=True): + if not criticality: + return [] + tag_patterns = (criticality.critical_tags + if critical else criticality.non_critical_tags) + return [self._get_critical_stat(p, critical) for p in tag_patterns] + + def _get_critical_stat(self, pattern, critical): + name = unicode(pattern) + return CriticalTagStat(pattern, name, critical, self.get_doc(name), + self.get_links(name)) + + def get_combined_stats(self, combined=None): + return [self._get_combined_stat(*comb) for comb in combined or []] + + def _get_combined_stat(self, pattern, name=None): + name = name or pattern + return CombinedTagStat(pattern, name, self.get_doc(name), + self.get_links(name)) + + def get_doc(self, tag): + return ' & '.join(doc.text for doc in self._docs if doc.match(tag)) + + def get_links(self, tag): + return [link.get_link(tag) for link in self._links if link.match(tag)] + + +class TagStatDoc(object): + + def __init__(self, pattern, doc): + self._matcher = TagPatterns(pattern) + self.text = doc + + def match(self, tag): + return self._matcher.match(tag) + + +class TagStatLink(object): + _match_pattern_tokenizer = re.compile('(\*|\?+)') + + def __init__(self, pattern, link, title): + self._regexp = self._get_match_regexp(pattern) + self._link = link + self._title = title.replace('_', ' ') + + def match(self, tag): + return self._regexp.match(tag) is not None + + def get_link(self, tag): + match = self._regexp.match(tag) + if not match: + return None + link, title = self._replace_groups(self._link, self._title, match) + return link, title + + def _replace_groups(self, link, title, match): + for index, group in enumerate(match.groups()): + placefolder = '%%%d' % (index+1) + link = link.replace(placefolder, group) + title = title.replace(placefolder, group) + return link, title + + def _get_match_regexp(self, pattern): + pattern = '^%s$' % ''.join(self._yield_match_pattern(pattern)) + return re.compile(pattern, re.IGNORECASE) + + def _yield_match_pattern(self, pattern): + for token in self._match_pattern_tokenizer.split(pattern): + if token.startswith('?'): + yield '(%s)' % ('.'*len(token)) + elif token == '*': + yield '(.*)' + else: + yield re.escape(token) diff --git a/robot/lib/python3.8/site-packages/robot/model/testcase.py b/robot/lib/python3.8/site-packages/robot/model/testcase.py new file mode 100644 index 0000000000000000000000000000000000000000..3326359079abc2803fba9fd35d393b5e6686dcd6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/testcase.py @@ -0,0 +1,92 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import setter + +from .itemlist import ItemList +from .keyword import Keyword, Keywords +from .modelobject import ModelObject +from .tags import Tags + + +class TestCase(ModelObject): + """Base model for a single test case. + + Extended by :class:`robot.running.model.TestCase` and + :class:`robot.result.model.TestCase`. + """ + __slots__ = ['parent', 'name', 'doc', 'timeout'] + keyword_class = Keyword #: Internal usage only + + def __init__(self, name='', doc='', tags=None, timeout=None): + self.parent = None #: Parent suite. + self.name = name #: Test case name. + self.doc = doc #: Test case documentation. + self.timeout = timeout #: Test case timeout. + self.tags = tags + self.keywords = None + + @setter + def tags(self, tags): + """Test tags as a :class:`~.model.tags.Tags` object.""" + return Tags(tags) + + @setter + def keywords(self, keywords): + """Keywords as a :class:`~.Keywords` object. + + Contains also possible setup and teardown keywords. + """ + return Keywords(self.keyword_class, self, keywords) + + @property + def id(self): + """Test case id in format like ``s1-t3``. + + See :attr:`TestSuite.id ` for + more information. + """ + if not self.parent: + return 't1' + return '%s-t%d' % (self.parent.id, self.parent.tests.index(self)+1) + + @property + def longname(self): + """Test name prefixed with the long name of the parent suite.""" + if not self.parent: + return self.name + return '%s.%s' % (self.parent.longname, self.name) + + @property + def source(self): + return self.parent.source if self.parent is not None else None + + def visit(self, visitor): + """:mod:`Visitor interface ` entry-point.""" + visitor.visit_test(self) + + +class TestCases(ItemList): + __slots__ = [] + + def __init__(self, test_class=TestCase, parent=None, tests=None): + ItemList.__init__(self, test_class, {'parent': parent}, tests) + + def _check_type_and_set_attrs(self, *tests): + tests = ItemList._check_type_and_set_attrs(self, *tests) + for test in tests: + for visitor in test.parent._visitors: + test.visit(visitor) + return tests diff --git a/robot/lib/python3.8/site-packages/robot/model/testsuite.py b/robot/lib/python3.8/site-packages/robot/model/testsuite.py new file mode 100644 index 0000000000000000000000000000000000000000..cd27281ab532fe9333330980cb821e0f0aa48560 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/testsuite.py @@ -0,0 +1,181 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import setter + +from .configurer import SuiteConfigurer +from .filter import Filter, EmptySuiteRemover +from .itemlist import ItemList +from .keyword import Keyword, Keywords +from .metadata import Metadata +from .modelobject import ModelObject +from .tagsetter import TagSetter +from .testcase import TestCase, TestCases + + +class TestSuite(ModelObject): + """Base model for single suite. + + Extended by :class:`robot.running.model.TestSuite` and + :class:`robot.result.model.TestSuite`. + """ + __slots__ = ['parent', 'source', '_name', 'doc', '_my_visitors', 'rpa'] + test_class = TestCase #: Internal usage only. + keyword_class = Keyword #: Internal usage only. + + def __init__(self, name='', doc='', metadata=None, source=None, rpa=False): + self.parent = None #: Parent suite. ``None`` with the root suite. + self._name = name + self.doc = doc #: Test suite documentation. + self.metadata = metadata + self.source = source #: Path to the source file or directory. + self.rpa = rpa + self.suites = None + self.tests = None + self.keywords = None + self._my_visitors = [] + + @property + def _visitors(self): + parent_visitors = self.parent._visitors if self.parent else [] + return self._my_visitors + parent_visitors + + @property + def name(self): + """Test suite name. If not set, constructed from child suite names.""" + return self._name or ' & '.join(s.name for s in self.suites) + + @name.setter + def name(self, name): + self._name = name + + @property + def longname(self): + """Suite name prefixed with the long name of the parent suite.""" + if not self.parent: + return self.name + return '%s.%s' % (self.parent.longname, self.name) + + @setter + def metadata(self, metadata): + """Free test suite metadata as a dictionary.""" + return Metadata(metadata) + + @setter + def suites(self, suites): + """Child suites as a :class:`~.TestSuites` object.""" + return TestSuites(self.__class__, self, suites) + + @setter + def tests(self, tests): + """Tests as a :class:`~.TestCases` object.""" + return TestCases(self.test_class, self, tests) + + @setter + def keywords(self, keywords): + """Suite setup and teardown as a :class:`~.Keywords` object.""" + return Keywords(self.keyword_class, self, keywords) + + @property + def id(self): + """An automatically generated unique id. + + The root suite has id ``s1``, its child suites have ids ``s1-s1``, + ``s1-s2``, ..., their child suites get ids ``s1-s1-s1``, ``s1-s1-s2``, + ..., ``s1-s2-s1``, ..., and so on. + + The first test in a suite has an id like ``s1-t1``, the second has an + id ``s1-t2``, and so on. Similarly keywords in suites (setup/teardown) + and in tests get ids like ``s1-k1``, ``s1-t1-k1``, and ``s1-s4-t2-k5``. + """ + if not self.parent: + return 's1' + return '%s-s%d' % (self.parent.id, self.parent.suites.index(self)+1) + + @property + def test_count(self): + """Number of the tests in this suite, recursively.""" + return len(self.tests) + sum(suite.test_count for suite in self.suites) + + @property + def has_tests(self): + if self.tests: + return True + return any(s.has_tests for s in self.suites) + + def set_tags(self, add=None, remove=None, persist=False): + """Add and/or remove specified tags to the tests in this suite. + + :param add: Tags to add as a list or, if adding only one, + as a single string. + :param remove: Tags to remove as a list or as a single string. + Can be given as patterns where ``*`` and ``?`` work as wildcards. + :param persist: Add/remove specified tags also to new tests added + to this suite in the future. + """ + setter = TagSetter(add, remove) + self.visit(setter) + if persist: + self._my_visitors.append(setter) + + def filter(self, included_suites=None, included_tests=None, + included_tags=None, excluded_tags=None): + """Select test cases and remove others from this suite. + + Parameters have the same semantics as ``--suite``, ``--test``, + ``--include``, and ``--exclude`` command line options. All of them + can be given as a list of strings, or when selecting only one, as + a single string. + + Child suites that contain no tests after filtering are automatically + removed. + + Example:: + + suite.filter(included_tests=['Test 1', '* Example'], + included_tags='priority-1') + """ + self.visit(Filter(included_suites, included_tests, + included_tags, excluded_tags)) + + def configure(self, **options): + """A shortcut to configure a suite using one method call. + + Can only be used with the root test suite. + + :param options: Passed to + :class:`~robot.model.configurer.SuiteConfigurer` that will then + set suite attributes, call :meth:`filter`, etc. as needed. + """ + if self.parent is not None: + raise ValueError("'TestSuite.configure()' can only be used with " + "the root test suite.") + if options: + self.visit(SuiteConfigurer(**options)) + + def remove_empty_suites(self, preserve_direct_children=False): + """Removes all child suites not containing any tests, recursively.""" + self.visit(EmptySuiteRemover(preserve_direct_children)) + + def visit(self, visitor): + """:mod:`Visitor interface ` entry-point.""" + visitor.visit_suite(self) + + +class TestSuites(ItemList): + __slots__ = [] + + def __init__(self, suite_class=TestSuite, parent=None, suites=None): + ItemList.__init__(self, suite_class, {'parent': parent}, suites) diff --git a/robot/lib/python3.8/site-packages/robot/model/totalstatistics.py b/robot/lib/python3.8/site-packages/robot/model/totalstatistics.py new file mode 100644 index 0000000000000000000000000000000000000000..4ace46263f302b1a6e00295d10506f28d7a09a0a --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/totalstatistics.py @@ -0,0 +1,75 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .stats import TotalStat +from .visitor import SuiteVisitor + + +class TotalStatistics(object): + """Container for total statistics.""" + + def __init__(self, rpa=False): + #: Instance of :class:`~robot.model.stats.TotalStat` for critical tests. + test_or_task = 'Tests' if not rpa else 'Tasks' + self.critical = TotalStat('Critical ' + test_or_task) + #: Instance of :class:`~robot.model.stats.TotalStat` for all the tests. + self.all = TotalStat('All ' + test_or_task) + self._rpa = rpa + + def visit(self, visitor): + visitor.visit_total_statistics(self) + + def __iter__(self): + return iter([self.critical, self.all]) + + @property + def message(self): + """String representation of the statistics. + + For example:: + + 2 critical tests, 1 passed, 1 failed + 2 tests total, 1 passed, 1 failed + """ + test_or_task = 'test' if not self._rpa else 'task' + ctotal, cend, cpass, cfail = self._get_counts(self.critical) + atotal, aend, apass, afail = self._get_counts(self.all) + return ('%d critical %s%s, %d passed, %d failed\n' + '%d %s%s total, %d passed, %d failed' + % (ctotal, test_or_task, cend, cpass, cfail, + atotal, test_or_task, aend, apass, afail)) + + def _get_counts(self, stat): + ending = 's' if stat.total != 1 else '' + return stat.total, ending, stat.passed, stat.failed + + +class TotalStatisticsBuilder(SuiteVisitor): + + def __init__(self, suite=None, rpa=False): + self.stats = TotalStatistics(rpa) + if suite: + suite.visit(self) + + def add_test(self, test): + self.stats.all.add_test(test) + if test.critical: + self.stats.critical.add_test(test) + + def visit_test(self, test): + self.add_test(test) + + def visit_keyword(self, kw): + pass diff --git a/robot/lib/python3.8/site-packages/robot/model/visitor.py b/robot/lib/python3.8/site-packages/robot/model/visitor.py new file mode 100644 index 0000000000000000000000000000000000000000..670b7b30c56c9299d0b944bd51ea5f40ddc06682 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/model/visitor.py @@ -0,0 +1,164 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Interface to ease traversing through a test suite structure. + +Visitors make it easy to modify test suite structures or to collect information +from them. They work both with the :mod:`executable model ` +and the :mod:`result model `, but the objects passed to +the visitor methods are slightly different depending on the model they are +used with. The main differences are that on the execution side keywords do +not have child keywords nor messages, and that only the result objects have +status related attributes like :attr:`status` and :attr:`starttime`. + +This module contains :class:`SuiteVisitor` that implements the core logic to +visit a test suite structure, and the :mod:`~robot.result` package contains +:class:`~robot.result.visitor.ResultVisitor` that supports visiting the whole +test execution result structure. Both of these visitors should be imported +via the :mod:`robot.api` package when used by external code. + +Visitor algorithm +----------------- + +All suite, test, keyword and message objects have a :meth:`visit` method that +accepts a visitor instance. These methods will then call the correct visitor +method :meth:`~SuiteVisitor.visit_suite`, :meth:`~SuiteVisitor.visit_test`, +:meth:`~SuiteVisitor.visit_keyword` or :meth:`~SuiteVisitor.visit_message`, +depending on the instance where the :meth:`visit` method exists. + +The recommended and definitely easiest way to implement a visitor is extending +the :class:`SuiteVisitor` base class. The default implementation of its +:meth:`visit_x` methods take care of traversing child elements of the object +:obj:`x` recursively. A :meth:`visit_x` method first calls a corresponding +:meth:`start_x` method (e.g. :meth:`visit_suite` calls :meth:`start_suite`), +then calls :meth:`visit` for all child objects of the :obj:`x` object, and +finally calls the corresponding :meth:`end_x` method. The default +implementations of :meth:`start_x` and :meth:`end_x` do nothing. + +Visitors extending the :class:`SuiteVisitor` can stop visiting at a certain +level either by overriding suitable :meth:`visit_x` method or by returning +an explicit ``False`` from any :meth:`start_x` method. + +Examples +-------- + +The following example visitor modifies the test suite structure it visits. +It could be used, for example, with Robot Framework's ``--prerunmodifier`` +option to modify test data before execution. + +.. literalinclude:: ../../../doc/api/code_examples/SelectEveryXthTest.py + :language: python + +For more examples it is possible to look at the source code of visitors used +internally by Robot Framework itself. Some good examples are +:class:`~robot.model.tagsetter.TagSetter` and +:mod:`keyword removers `. +""" + + +class SuiteVisitor(object): + """Abstract class to ease traversing through the test suite structure. + + See the :mod:`module level ` documentation for more + information and an example. + """ + + def visit_suite(self, suite): + """Implements traversing through the suite and its direct children. + + Can be overridden to allow modifying the passed in ``suite`` without + calling :func:`start_suite` or :func:`end_suite` nor visiting child + suites, tests or keywords (setup and teardown) at all. + """ + if self.start_suite(suite) is not False: + suite.keywords.visit(self) + suite.suites.visit(self) + suite.tests.visit(self) + self.end_suite(suite) + + def start_suite(self, suite): + """Called when suite starts. Default implementation does nothing. + + Can return explicit ``False`` to stop visiting. + """ + pass + + def end_suite(self, suite): + """Called when suite ends. Default implementation does nothing.""" + pass + + def visit_test(self, test): + """Implements traversing through the test and its keywords. + + Can be overridden to allow modifying the passed in ``test`` without + calling :func:`start_test` or :func:`end_test` nor visiting keywords. + """ + if self.start_test(test) is not False: + test.keywords.visit(self) + self.end_test(test) + + def start_test(self, test): + """Called when test starts. Default implementation does nothing. + + Can return explicit ``False`` to stop visiting. + """ + pass + + def end_test(self, test): + """Called when test ends. Default implementation does nothing.""" + pass + + def visit_keyword(self, kw): + """Implements traversing through the keyword and its child keywords. + + Can be overridden to allow modifying the passed in ``kw`` without + calling :func:`start_keyword` or :func:`end_keyword` nor visiting + child keywords. + """ + if self.start_keyword(kw) is not False: + kw.keywords.visit(self) + kw.messages.visit(self) + self.end_keyword(kw) + + def start_keyword(self, keyword): + """Called when keyword starts. Default implementation does nothing. + + Can return explicit ``False`` to stop visiting. + """ + pass + + def end_keyword(self, keyword): + """Called when keyword ends. Default implementation does nothing.""" + pass + + def visit_message(self, msg): + """Implements visiting the message. + + Can be overridden to allow modifying the passed in ``msg`` without + calling :func:`start_message` or :func:`end_message`. + """ + if self.start_message(msg) is not False: + self.end_message(msg) + + def start_message(self, msg): + """Called when message starts. Default implementation does nothing. + + Can return explicit ``False`` to stop visiting. + """ + pass + + def end_message(self, msg): + """Called when message ends. Default implementation does nothing.""" + pass diff --git a/robot/lib/python3.8/site-packages/robot/output/__init__.py b/robot/lib/python3.8/site-packages/robot/output/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7c782c864d886036fb867c764c072b19a115e601 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/__init__.py @@ -0,0 +1,25 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Package for internal logging and other output. + +Not part of the public API, and also subject to change in the future when +test execution is refactored. +""" + +from .output import Output +from .logger import LOGGER +from .xmllogger import XmlLogger +from .loggerhelper import LEVELS, Message diff --git a/robot/lib/python3.8/site-packages/robot/output/console/__init__.py b/robot/lib/python3.8/site-packages/robot/output/console/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..813c58342fccbd4f933c32c78eab2542d809924a --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/console/__init__.py @@ -0,0 +1,35 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError + +from .dotted import DottedOutput +from .quiet import NoOutput, QuietOutput +from .verbose import VerboseOutput + + +def ConsoleOutput(type='verbose', width=78, colors='AUTO', markers='AUTO', + stdout=None, stderr=None): + upper = type.upper() + if upper == 'VERBOSE': + return VerboseOutput(width, colors, markers, stdout, stderr) + if upper == 'DOTTED': + return DottedOutput(width, colors, stdout, stderr) + if upper == 'QUIET': + return QuietOutput(colors, stderr) + if upper == 'NONE': + return NoOutput() + raise DataError("Invalid console output type '%s'. Available " + "'VERBOSE', 'DOTTED', 'QUIET' and 'NONE'." % type) diff --git a/robot/lib/python3.8/site-packages/robot/output/console/dotted.py b/robot/lib/python3.8/site-packages/robot/output/console/dotted.py new file mode 100644 index 0000000000000000000000000000000000000000..47d601332ff1ec4e3f6bfd2c2c018a3768c02aae --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/console/dotted.py @@ -0,0 +1,90 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from robot.model import SuiteVisitor +from robot.utils import plural_or_not, secs_to_timestr + +from .highlighting import HighlightingStream + + +class DottedOutput(object): + + def __init__(self, width=78, colors='AUTO', stdout=None, stderr=None): + self._width = width + self._stdout = HighlightingStream(stdout or sys.__stdout__, colors) + self._stderr = HighlightingStream(stderr or sys.__stderr__, colors) + self._markers_on_row = 0 + + def start_suite(self, suite): + if not suite.parent: + self._stdout.write("Running suite '%s' with %d %s%s.\n" + % (suite.name, suite.test_count, + 'test' if not suite.rpa else 'task', + plural_or_not(suite.test_count))) + self._stdout.write('=' * self._width + '\n') + + def end_test(self, test): + if self._markers_on_row == self._width: + self._stdout.write('\n') + self._markers_on_row = 0 + self._markers_on_row += 1 + if test.passed: + self._stdout.write('.') + elif 'robot:exit' in test.tags: + self._stdout.write('x') + elif not test.critical: + self._stdout.write('f') + else: + self._stdout.highlight('F', 'FAIL') + + def end_suite(self, suite): + if not suite.parent: + self._stdout.write('\n') + StatusReporter(self._stdout, self._width).report(suite) + self._stdout.write('\n') + + def message(self, msg): + if msg.level in ('WARN', 'ERROR'): + self._stderr.error(msg.message, msg.level) + + def output_file(self, name, path): + self._stdout.write('%-8s %s\n' % (name+':', path)) + + +class StatusReporter(SuiteVisitor): + + def __init__(self, stream, width): + self._stream = stream + self._width = width + + def report(self, suite): + suite.visit(self) + stats = suite.statistics + self._stream.write("%s\nRun suite '%s' with %d %s%s in %s.\n\n" + % ('=' * self._width, suite.name, stats.all.total, + 'test' if not suite.rpa else 'task', + plural_or_not(stats.all.total), + secs_to_timestr(suite.elapsedtime/1000.0))) + self._stream.highlight(suite.status + 'ED', suite.status) + self._stream.write('\n%s\n' % stats.message) + + def visit_test(self, test): + if not test.passed and test.critical and 'robot:exit' not in test.tags: + self._stream.write('-' * self._width + '\n') + self._stream.highlight('FAIL') + self._stream.write(': %s\n%s\n' % (test.longname, + test.message.strip())) diff --git a/robot/lib/python3.8/site-packages/robot/output/console/highlighting.py b/robot/lib/python3.8/site-packages/robot/output/console/highlighting.py new file mode 100644 index 0000000000000000000000000000000000000000..2942ec6a51d386cf1d9123185a2cd6303a95f71e --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/console/highlighting.py @@ -0,0 +1,212 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Windows highlighting code adapted from color_console.py. It is copyright +# Andre Burgaud, licensed under the MIT License, and available here: +# http://www.burgaud.com/bring-colors-to-the-windows-console-with-python/ + +from contextlib import contextmanager +import errno +import os +import sys +try: + from ctypes import windll, Structure, c_short, c_ushort, byref +except ImportError: # Not on Windows or using Jython + windll = None + +from robot.errors import DataError +from robot.utils import console_encode, isatty, WINDOWS + + +class HighlightingStream(object): + + def __init__(self, stream, colors='AUTO'): + self.stream = stream + self._highlighter = self._get_highlighter(stream, colors) + + def _get_highlighter(self, stream, colors): + options = {'AUTO': Highlighter if isatty(stream) else NoHighlighting, + 'ON': Highlighter, + 'OFF': NoHighlighting, + 'ANSI': AnsiHighlighter} + try: + highlighter = options[colors.upper()] + except KeyError: + raise DataError("Invalid console color value '%s'. Available " + "'AUTO', 'ON', 'OFF' and 'ANSI'." % colors) + return highlighter(stream) + + def write(self, text, flush=True): + self._write(console_encode(text, stream=self.stream)) + if flush: + self.flush() + + def _write(self, text, retry=5): + # Workaround for Windows 10 console bug: + # https://github.com/robotframework/robotframework/issues/2709 + try: + with self._suppress_broken_pipe_error: + self.stream.write(text) + except IOError as err: + if not (WINDOWS and err.errno == 0 and retry > 0): + raise + self._write(text, retry-1) + + @property + @contextmanager + def _suppress_broken_pipe_error(self): + try: + yield + except IOError as err: + if err.errno not in (errno.EPIPE, errno.EINVAL, errno.EBADF): + raise + + def flush(self): + with self._suppress_broken_pipe_error: + self.stream.flush() + + def highlight(self, text, status=None, flush=True): + if self._must_flush_before_and_after_highlighting(): + self.flush() + flush = True + with self._highlighting(status or text): + self.write(text, flush) + + def _must_flush_before_and_after_highlighting(self): + # Must flush on Windows before and after highlighting to make sure set + # console colors only affect the actual highlighted text. Problems + # only encountered with Python 3, but better to be safe than sorry. + return WINDOWS and not isinstance(self._highlighter, NoHighlighting) + + def error(self, message, level): + self.write('[ ', flush=False) + self.highlight(level, flush=False) + self.write(' ] %s\n' % message) + + @contextmanager + def _highlighting(self, status): + highlighter = self._highlighter + start = {'PASS': highlighter.green, + 'FAIL': highlighter.red, + 'ERROR': highlighter.red, + 'WARN': highlighter.yellow}[status] + start() + try: + yield + finally: + highlighter.reset() + + +def Highlighter(stream): + if os.sep == '/': + return AnsiHighlighter(stream) + return DosHighlighter(stream) if windll else NoHighlighting(stream) + + +class AnsiHighlighter(object): + _ANSI_GREEN = '\033[32m' + _ANSI_RED = '\033[31m' + _ANSI_YELLOW = '\033[33m' + _ANSI_RESET = '\033[0m' + + def __init__(self, stream): + self._stream = stream + + def green(self): + self._set_color(self._ANSI_GREEN) + + def red(self): + self._set_color(self._ANSI_RED) + + def yellow(self): + self._set_color(self._ANSI_YELLOW) + + def reset(self): + self._set_color(self._ANSI_RESET) + + def _set_color(self, color): + self._stream.write(color) + + +class NoHighlighting(AnsiHighlighter): + + def _set_color(self, color): + pass + + +class DosHighlighter(object): + _FOREGROUND_GREEN = 0x2 + _FOREGROUND_RED = 0x4 + _FOREGROUND_YELLOW = 0x6 + _FOREGROUND_GREY = 0x7 + _FOREGROUND_INTENSITY = 0x8 + _BACKGROUND_MASK = 0xF0 + _STDOUT_HANDLE = -11 + _STDERR_HANDLE = -12 + + def __init__(self, stream): + self._handle = self._get_std_handle(stream) + self._orig_colors = self._get_colors() + self._background = self._orig_colors & self._BACKGROUND_MASK + + def green(self): + self._set_foreground_colors(self._FOREGROUND_GREEN) + + def red(self): + self._set_foreground_colors(self._FOREGROUND_RED) + + def yellow(self): + self._set_foreground_colors(self._FOREGROUND_YELLOW) + + def reset(self): + self._set_colors(self._orig_colors) + + def _get_std_handle(self, stream): + handle = self._STDOUT_HANDLE \ + if stream is sys.__stdout__ else self._STDERR_HANDLE + return windll.kernel32.GetStdHandle(handle) + + def _get_colors(self): + csbi = _CONSOLE_SCREEN_BUFFER_INFO() + ok = windll.kernel32.GetConsoleScreenBufferInfo(self._handle, byref(csbi)) + if not ok: # Call failed, return default console colors (gray on black) + return self._FOREGROUND_GREY + return csbi.wAttributes + + def _set_foreground_colors(self, colors): + self._set_colors(colors | self._FOREGROUND_INTENSITY | self._background) + + def _set_colors(self, colors): + windll.kernel32.SetConsoleTextAttribute(self._handle, colors) + + +if windll: + + class _COORD(Structure): + _fields_ = [("X", c_short), + ("Y", c_short)] + + class _SMALL_RECT(Structure): + _fields_ = [("Left", c_short), + ("Top", c_short), + ("Right", c_short), + ("Bottom", c_short)] + + class _CONSOLE_SCREEN_BUFFER_INFO(Structure): + _fields_ = [("dwSize", _COORD), + ("dwCursorPosition", _COORD), + ("wAttributes", c_ushort), + ("srWindow", _SMALL_RECT), + ("dwMaximumWindowSize", _COORD)] diff --git a/robot/lib/python3.8/site-packages/robot/output/console/quiet.py b/robot/lib/python3.8/site-packages/robot/output/console/quiet.py new file mode 100644 index 0000000000000000000000000000000000000000..73c7131423efd5d786492f36ad704e2cab552295 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/console/quiet.py @@ -0,0 +1,32 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from .highlighting import HighlightingStream + + +class QuietOutput(object): + + def __init__(self, colors='AUTO', stderr=None): + self._stderr = HighlightingStream(stderr or sys.__stderr__, colors) + + def message(self, msg): + if msg.level in ('WARN', 'ERROR'): + self._stderr.error(msg.message, msg.level) + + +class NoOutput(object): + pass diff --git a/robot/lib/python3.8/site-packages/robot/output/console/verbose.py b/robot/lib/python3.8/site-packages/robot/output/console/verbose.py new file mode 100644 index 0000000000000000000000000000000000000000..5341106f351a65176bd1eec20577b36a0a341644 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/console/verbose.py @@ -0,0 +1,177 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from robot.errors import DataError +from robot.utils import (get_console_length, getshortdoc, isatty, + pad_console_length) + +from .highlighting import HighlightingStream + + +class VerboseOutput(object): + + def __init__(self, width=78, colors='AUTO', markers='AUTO', stdout=None, + stderr=None): + self._writer = VerboseWriter(width, colors, markers, stdout, stderr) + self._started = False + self._started_keywords = 0 + self._running_test = False + + def start_suite(self, suite): + if not self._started: + self._writer.suite_separator() + self._started = True + self._writer.info(suite.longname, suite.doc, start_suite=True) + self._writer.suite_separator() + + def end_suite(self, suite): + self._writer.info(suite.longname, suite.doc) + self._writer.status(suite.status) + self._writer.message(suite.full_message) + self._writer.suite_separator() + + def start_test(self, test): + self._writer.info(test.name, test.doc) + self._running_test = True + + def end_test(self, test): + self._writer.status(test.status, clear=True) + self._writer.message(test.message) + self._writer.test_separator() + self._running_test = False + + def start_keyword(self, kw): + self._started_keywords += 1 + + def end_keyword(self, kw): + self._started_keywords -= 1 + if self._running_test and not self._started_keywords: + self._writer.keyword_marker(kw.status) + + def message(self, msg): + if msg.level in ('WARN', 'ERROR'): + self._writer.error(msg.message, msg.level, clear=self._running_test) + + def output_file(self, name, path): + self._writer.output(name, path) + + +class VerboseWriter(object): + _status_length = len('| PASS |') + + def __init__(self, width=78, colors='AUTO', markers='AUTO', stdout=None, + stderr=None): + self._width = width + self._stdout = HighlightingStream(stdout or sys.__stdout__, colors) + self._stderr = HighlightingStream(stderr or sys.__stderr__, colors) + self._keyword_marker = KeywordMarker(self._stdout, markers) + self._last_info = None + + def info(self, name, doc, start_suite=False): + width, separator = self._get_info_width_and_separator(start_suite) + self._last_info = self._get_info(name, doc, width) + separator + self._write_info() + self._keyword_marker.reset_count() + + def _write_info(self): + self._stdout.write(self._last_info) + + def _get_info_width_and_separator(self, start_suite): + if start_suite: + return self._width, '\n' + return self._width - self._status_length - 1, ' ' + + def _get_info(self, name, doc, width): + if get_console_length(name) > width: + return pad_console_length(name, width) + doc = getshortdoc(doc, linesep=' ') + info = '%s :: %s' % (name, doc) if doc else name + return pad_console_length(info, width) + + def suite_separator(self): + self._fill('=') + + def test_separator(self): + self._fill('-') + + def _fill(self, char): + self._stdout.write('%s\n' % (char * self._width)) + + def status(self, status, clear=False): + if self._should_clear_markers(clear): + self._clear_status() + self._stdout.write('| ', flush=False) + self._stdout.highlight(status, flush=False) + self._stdout.write(' |\n') + + def _should_clear_markers(self, clear): + return clear and self._keyword_marker.marking_enabled + + def _clear_status(self): + self._clear_info() + self._write_info() + + def _clear_info(self): + self._stdout.write('\r%s\r' % (' ' * self._width)) + self._keyword_marker.reset_count() + + def message(self, message): + if message: + self._stdout.write(message.strip() + '\n') + + def keyword_marker(self, status): + if self._keyword_marker.marker_count == self._status_length: + self._clear_status() + self._keyword_marker.reset_count() + self._keyword_marker.mark(status) + + def error(self, message, level, clear=False): + if self._should_clear_markers(clear): + self._clear_info() + self._stderr.error(message, level) + if self._should_clear_markers(clear): + self._write_info() + + def output(self, name, path): + self._stdout.write('%-8s %s\n' % (name+':', path)) + + +class KeywordMarker(object): + + def __init__(self, highlighter, markers): + self._highlighter = highlighter + self.marking_enabled = self._marking_enabled(markers, highlighter) + self.marker_count = 0 + + def _marking_enabled(self, markers, highlighter): + options = {'AUTO': isatty(highlighter.stream), + 'ON': True, + 'OFF': False} + try: + return options[markers.upper()] + except KeyError: + raise DataError("Invalid console marker value '%s'. Available " + "'AUTO', 'ON' and 'OFF'." % markers) + + def mark(self, status): + if self.marking_enabled: + marker, status = ('.', 'PASS') if status != 'FAIL' else ('F', 'FAIL') + self._highlighter.highlight(marker, status) + self.marker_count += 1 + + def reset_count(self): + self.marker_count = 0 diff --git a/robot/lib/python3.8/site-packages/robot/output/debugfile.py b/robot/lib/python3.8/site-packages/robot/output/debugfile.py new file mode 100644 index 0000000000000000000000000000000000000000..6f760c606289102a9a8ca04a4c1d2f0a66249142 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/debugfile.py @@ -0,0 +1,113 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.utils import get_timestamp, file_writer, seq2str2 + +from .logger import LOGGER +from .loggerhelper import IsLogged + + +def DebugFile(path): + if not path: + LOGGER.info('No debug file') + return None + try: + outfile = file_writer(path, usage='debug') + except DataError as err: + LOGGER.error(err.message) + return None + else: + LOGGER.info('Debug file: %s' % path) + return _DebugFileWriter(outfile) + + +class _DebugFileWriter: + _separators = {'SUITE': '=', 'TEST': '-', 'KW': '~'} + _setup_or_teardown = ('setup', 'teardown') + + def __init__(self, outfile): + self._indent = 0 + self._kw_level = 0 + self._separator_written_last = False + self._outfile = outfile + self._is_logged = IsLogged('DEBUG') + + def start_suite(self, suite): + self._separator('SUITE') + self._start('SUITE', suite.longname) + self._separator('SUITE') + + def end_suite(self, suite): + self._separator('SUITE') + self._end('SUITE', suite.longname, suite.elapsedtime) + self._separator('SUITE') + if self._indent == 0: + LOGGER.output_file('Debug', self._outfile.name) + self.close() + + def start_test(self, test): + self._separator('TEST') + self._start('TEST', test.name) + self._separator('TEST') + + def end_test(self, test): + self._separator('TEST') + self._end('TEST', test.name, test.elapsedtime) + self._separator('TEST') + + def start_keyword(self, kw): + if self._kw_level == 0: + self._separator('KW') + self._start(self._get_kw_type(kw), kw.name, kw.args) + self._kw_level += 1 + + def end_keyword(self, kw): + self._end(self._get_kw_type(kw), kw.name, kw.elapsedtime) + self._kw_level -= 1 + + def log_message(self, msg): + if self._is_logged(msg.level): + self._write(msg.message, level=msg.level, timestamp=msg.timestamp) + + def close(self): + if not self._outfile.closed: + self._outfile.close() + + def _get_kw_type(self, kw): + if kw.type in self._setup_or_teardown: + return kw.type.upper() + return 'KW' + + def _start(self, type_, name, args=''): + args = ' ' + seq2str2(args) + self._write('+%s START %s: %s%s' % ('-'*self._indent, type_, name, args)) + self._indent += 1 + + def _end(self, type_, name, elapsed): + self._indent -= 1 + self._write('+%s END %s: %s (%s)' % ('-'*self._indent, type_, name, elapsed)) + + def _separator(self, type_): + self._write(self._separators[type_] * 78, separator=True) + + def _write(self, text, separator=False, level='INFO', timestamp=None): + if separator and self._separator_written_last: + return + if not separator: + text = '%s - %s - %s' % (timestamp or get_timestamp(), level, text) + self._outfile.write(text.rstrip() + '\n') + self._outfile.flush() + self._separator_written_last = separator diff --git a/robot/lib/python3.8/site-packages/robot/output/filelogger.py b/robot/lib/python3.8/site-packages/robot/output/filelogger.py new file mode 100644 index 0000000000000000000000000000000000000000..773883ca98e705211101cd25ff4087064ffc49e9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/filelogger.py @@ -0,0 +1,58 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import file_writer + +from .loggerhelper import AbstractLogger + + +class FileLogger(AbstractLogger): + + def __init__(self, path, level): + AbstractLogger.__init__(self, level) + self._writer = self._get_writer(path) # unit test hook + + def _get_writer(self, path): + return file_writer(path, usage='syslog') + + def message(self, msg): + if self._is_logged(msg.level) and not self._writer.closed: + entry = '%s | %s | %s\n' % (msg.timestamp, msg.level.ljust(5), + msg.message) + self._writer.write(entry) + + def start_suite(self, suite): + self.info("Started test suite '%s'" % suite.name) + + def end_suite(self, suite): + self.info("Ended test suite '%s'" % suite.name) + + def start_test(self, test): + self.info("Started test case '%s'" % test.name) + + def end_test(self, test): + self.info("Ended test case '%s'" % test.name) + + def start_keyword(self, kw): + self.debug(lambda: "Started keyword '%s'" % kw.name) + + def end_keyword(self, kw): + self.debug(lambda: "Ended keyword '%s'" % kw.name) + + def output_file(self, name, path): + self.info('%s: %s' % (name, path)) + + def close(self): + self._writer.close() diff --git a/robot/lib/python3.8/site-packages/robot/output/librarylogger.py b/robot/lib/python3.8/site-packages/robot/output/librarylogger.py new file mode 100644 index 0000000000000000000000000000000000000000..919e4aac6bdc90e16e66bcffd4662a8435d3a2b6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/librarylogger.py @@ -0,0 +1,75 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Implementation of the public test library logging API. + +This is exposed via :py:mod:`robot.api.logger`. Implementation must reside +here to avoid cyclic imports. +""" + +import sys +import threading + +from robot.errors import DataError +from robot.utils import unic, console_encode + +from .logger import LOGGER +from .loggerhelper import Message + + +LOGGING_THREADS = ('MainThread', 'RobotFrameworkTimeoutThread') + + +def write(msg, level, html=False): + # Callable messages allow lazy logging internally, but we don't want to + # expose this functionality publicly. See the following issue for details: + # https://github.com/robotframework/robotframework/issues/1505 + if callable(msg): + msg = unic(msg) + if level.upper() not in ('TRACE', 'DEBUG', 'INFO', 'HTML', 'WARN', 'ERROR'): + raise DataError("Invalid log level '%s'." % level) + if threading.currentThread().getName() in LOGGING_THREADS: + LOGGER.log_message(Message(msg, level, html)) + + +def trace(msg, html=False): + write(msg, 'TRACE', html) + + +def debug(msg, html=False): + write(msg, 'DEBUG', html) + + +def info(msg, html=False, also_console=False): + write(msg, 'INFO', html) + if also_console: + console(msg) + + +def warn(msg, html=False): + write(msg, 'WARN', html) + + +def error(msg, html=False): + write(msg, 'ERROR', html) + + +def console(msg, newline=True, stream='stdout'): + msg = unic(msg) + if newline: + msg += '\n' + stream = sys.__stdout__ if stream.lower() != 'stderr' else sys.__stderr__ + stream.write(console_encode(msg, stream=stream)) + stream.flush() diff --git a/robot/lib/python3.8/site-packages/robot/output/listenerarguments.py b/robot/lib/python3.8/site-packages/robot/output/listenerarguments.py new file mode 100644 index 0000000000000000000000000000000000000000..0f75dd78cb558bbf39d9c4073a8712d9d9b55408 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/listenerarguments.py @@ -0,0 +1,142 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import is_list_like, is_dict_like, is_string, unic + + +class ListenerArguments(object): + + def __init__(self, arguments): + self._arguments = arguments + self._version2 = None + self._version3 = None + + def get_arguments(self, version): + if version == 2: + if self._version2 is None: + self._version2 = self._get_version2_arguments(*self._arguments) + return self._version2 + else: + if self._version3 is None: + self._version3 = self._get_version3_arguments(*self._arguments) + return self._version3 + + def _get_version2_arguments(self, *arguments): + return arguments + + def _get_version3_arguments(self, *arguments): + return arguments + + @classmethod + def by_method_name(cls, name, arguments): + Arguments = {'start_suite': StartSuiteArguments, + 'end_suite': EndSuiteArguments, + 'start_test': StartTestArguments, + 'end_test': EndTestArguments, + 'start_keyword': StartKeywordArguments, + 'end_keyword': EndKeywordArguments, + 'log_message': MessageArguments, + 'message': MessageArguments}.get(name, ListenerArguments) + return Arguments(arguments) + + +class MessageArguments(ListenerArguments): + + def _get_version2_arguments(self, msg): + attributes = {'timestamp': msg.timestamp, + 'message': msg.message, + 'level': msg.level, + 'html': 'yes' if msg.html else 'no'} + return attributes, + + def _get_version3_arguments(self, msg): + return msg, + + +class _ListenerArgumentsFromItem(ListenerArguments): + _attribute_names = None + + def _get_version2_arguments(self, item): + attributes = dict((name, self._get_attribute_value(item, name)) + for name in self._attribute_names) + attributes.update(self._get_extra_attributes(item)) + return item.name, attributes + + def _get_attribute_value(self, item, name): + value = getattr(item, name) + return self._take_copy_of_mutable_value(value) + + def _take_copy_of_mutable_value(self, value): + if is_dict_like(value): + return dict(value) + if is_list_like(value): + return list(value) + return value + + def _get_extra_attributes(self, item): + return {} + + def _get_version3_arguments(self, item): + return item.data, item.result + + +class StartSuiteArguments(_ListenerArgumentsFromItem): + _attribute_names = ('id', 'longname', 'doc', 'metadata', 'starttime') + + def _get_extra_attributes(self, suite): + return {'tests': [t.name for t in suite.tests], + 'suites': [s.name for s in suite.suites], + 'totaltests': suite.test_count, + 'source': suite.source or ''} + + +class EndSuiteArguments(StartSuiteArguments): + _attribute_names = ('id', 'longname', 'doc', 'metadata', 'starttime', + 'endtime', 'elapsedtime', 'status', 'message') + + def _get_extra_attributes(self, suite): + attrs = StartSuiteArguments._get_extra_attributes(self, suite) + attrs['statistics'] = suite.stat_message + return attrs + + +class StartTestArguments(_ListenerArgumentsFromItem): + _attribute_names = ('id', 'longname', 'doc', 'tags', 'lineno', 'starttime') + + def _get_extra_attributes(self, test): + return {'critical': 'yes' if test.critical else 'no', + 'template': test.template or '', + 'originalname': test.data.name} + + +class EndTestArguments(StartTestArguments): + _attribute_names = ('id', 'longname', 'doc', 'tags', 'lineno', 'starttime', + 'endtime', 'elapsedtime', 'status', 'message') + + +class StartKeywordArguments(_ListenerArgumentsFromItem): + _attribute_names = ('kwname', 'libname', 'doc', 'assign', 'tags', + 'starttime') + _types = {'kw': 'Keyword', 'setup': 'Setup', 'teardown': 'Teardown', + 'for': 'For', 'foritem': 'For Item'} + + def _get_extra_attributes(self, kw): + args = [a if is_string(a) else unic(a) for a in kw.args] + return {'args': args, 'type': self._types[kw.type]} + + +class EndKeywordArguments(StartKeywordArguments): + _attribute_names = ('kwname', 'libname', 'doc', 'args', 'assign', 'tags', + 'starttime', 'endtime', 'elapsedtime', 'status') diff --git a/robot/lib/python3.8/site-packages/robot/output/listenermethods.py b/robot/lib/python3.8/site-packages/robot/output/listenermethods.py new file mode 100644 index 0000000000000000000000000000000000000000..8bece247bdb3be9be5ab3d63073b9674fe02d641 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/listenermethods.py @@ -0,0 +1,114 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import TimeoutError +from robot.utils import get_error_details, py2to3 + +from .listenerarguments import ListenerArguments +from .logger import LOGGER + + +@py2to3 +class ListenerMethods(object): + + def __init__(self, method_name, listeners): + self._methods = [] + self._method_name = method_name + if listeners: + self._register_methods(method_name, listeners) + + def _register_methods(self, method_name, listeners): + for listener in listeners: + method = getattr(listener, method_name) + if method: + self._methods.append(ListenerMethod(method, listener)) + + def __call__(self, *args): + if self._methods: + args = ListenerArguments.by_method_name(self._method_name, args) + for method in self._methods: + method(args.get_arguments(method.version)) + + def __nonzero__(self): + return bool(self._methods) + + +class LibraryListenerMethods(object): + + def __init__(self, method_name): + self._method_stack = [] + self._method_name = method_name + + def new_suite_scope(self): + self._method_stack.append([]) + + def discard_suite_scope(self): + self._method_stack.pop() + + def register(self, listeners, library): + methods = self._method_stack[-1] + for listener in listeners: + method = getattr(listener, self._method_name) + if method: + info = ListenerMethod(method, listener, library) + methods.append(info) + + def unregister(self, library): + methods = [m for m in self._method_stack[-1] if m.library is not library] + self._method_stack[-1] = methods + + def __call__(self, *args, **conf): + methods = self._get_methods(**conf) + if methods: + args = ListenerArguments.by_method_name(self._method_name, args) + for method in methods: + method(args.get_arguments(method.version)) + + def _get_methods(self, library=None): + if not (self._method_stack and self._method_stack[-1]): + return [] + methods = self._method_stack[-1] + if library: + return [m for m in methods if m.library is library] + return methods + + +class ListenerMethod(object): + # Flag to avoid recursive listener calls. + called = False + + def __init__(self, method, listener, library=None): + self.method = method + self.listener_name = listener.name + self.version = listener.version + self.library = library + + def __call__(self, args): + if self.called: + return + try: + ListenerMethod.called = True + self.method(*args) + except TimeoutError: + # Propagate possible timeouts: + # https://github.com/robotframework/robotframework/issues/2763 + raise + except: + message, details = get_error_details() + LOGGER.error("Calling method '%s' of listener '%s' failed: %s" + % (self.method.__name__, self.listener_name, message)) + LOGGER.info("Details:\n%s" % details) + finally: + ListenerMethod.called = False diff --git a/robot/lib/python3.8/site-packages/robot/output/listeners.py b/robot/lib/python3.8/site-packages/robot/output/listeners.py new file mode 100644 index 0000000000000000000000000000000000000000..f7a73608783e67ece31e53c9127ec3ff7851d465 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/listeners.py @@ -0,0 +1,168 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os.path + +from robot.errors import DataError +from robot.utils import (Importer, is_string, py2to3, + split_args_from_name_or_path, type_name) + +from .listenermethods import ListenerMethods, LibraryListenerMethods +from .loggerhelper import AbstractLoggerProxy, IsLogged +from .logger import LOGGER + + +@py2to3 +class Listeners(object): + _method_names = ('start_suite', 'end_suite', 'start_test', 'end_test', + 'start_keyword', 'end_keyword', 'log_message', 'message', + 'output_file', 'report_file', 'log_file', 'debug_file', + 'xunit_file', 'library_import', 'resource_import', + 'variables_import', 'close') + + def __init__(self, listeners, log_level='INFO'): + self._is_logged = IsLogged(log_level) + listeners = ListenerProxy.import_listeners(listeners, + self._method_names) + for name in self._method_names: + method = ListenerMethods(name, listeners) + if name.endswith(('_file', '_import', 'log_message')): + name = '_' + name + setattr(self, name, method) + + def set_log_level(self, level): + self._is_logged.set_level(level) + + def log_message(self, msg): + if self._is_logged(msg.level): + self._log_message(msg) + + def imported(self, import_type, name, attrs): + method = getattr(self, '_%s_import' % import_type.lower()) + method(name, attrs) + + def output_file(self, file_type, path): + method = getattr(self, '_%s_file' % file_type.lower()) + method(path) + + def __nonzero__(self): + return any(isinstance(method, ListenerMethods) and method + for method in self.__dict__.values()) + + +class LibraryListeners(object): + _method_names = ('start_suite', 'end_suite', 'start_test', 'end_test', + 'start_keyword', 'end_keyword', 'log_message', 'message', + 'close') + + def __init__(self, log_level='INFO'): + self._is_logged = IsLogged(log_level) + for name in self._method_names: + method = LibraryListenerMethods(name) + if name == 'log_message': + name = '_' + name + setattr(self, name, method) + + def register(self, listeners, library): + listeners = ListenerProxy.import_listeners(listeners, + self._method_names, + prefix='_', + raise_on_error=True) + for method in self._listener_methods(): + method.register(listeners, library) + + def _listener_methods(self): + return [method for method in self.__dict__.values() + if isinstance(method, LibraryListenerMethods)] + + def unregister(self, library, close=False): + if close: + self.close(library=library) + for method in self._listener_methods(): + method.unregister(library) + + def new_suite_scope(self): + for method in self._listener_methods(): + method.new_suite_scope() + + def discard_suite_scope(self): + for method in self._listener_methods(): + method.discard_suite_scope() + + def set_log_level(self, level): + self._is_logged.set_level(level) + + def log_message(self, msg): + if self._is_logged(msg.level): + self._log_message(msg) + + def imported(self, import_type, name, attrs): + pass + + def output_file(self, file_type, path): + pass + + +class ListenerProxy(AbstractLoggerProxy): + _no_method = None + + def __init__(self, listener, method_names, prefix=None): + listener, name = self._import_listener(listener) + AbstractLoggerProxy.__init__(self, listener, method_names, prefix) + self.name = name + self.version = self._get_version(listener) + if self.version == 3: + self.start_keyword = self.end_keyword = None + self.library_import = self.resource_import = self.variables_import = None + + def _import_listener(self, listener): + if not is_string(listener): + # Modules have `__name__`, with others better to use `type_name`. + name = getattr(listener, '__name__', None) or type_name(listener) + return listener, name + name, args = split_args_from_name_or_path(listener) + importer = Importer('listener') + listener = importer.import_class_or_module(os.path.normpath(name), + instantiate_with_args=args) + return listener, name + + def _get_version(self, listener): + try: + version = int(listener.ROBOT_LISTENER_API_VERSION) + if version not in (2, 3): + raise ValueError + except AttributeError: + raise DataError("Listener '%s' does not have mandatory " + "'ROBOT_LISTENER_API_VERSION' attribute." + % self.name) + except (ValueError, TypeError): + raise DataError("Listener '%s' uses unsupported API version '%s'." + % (self.name, listener.ROBOT_LISTENER_API_VERSION)) + return version + + @classmethod + def import_listeners(cls, listeners, method_names, prefix=None, + raise_on_error=False): + imported = [] + for listener in listeners: + try: + imported.append(cls(listener, method_names, prefix)) + except DataError as err: + name = listener if is_string(listener) else type_name(listener) + msg = "Taking listener '%s' into use failed: %s" % (name, err) + if raise_on_error: + raise DataError(msg) + LOGGER.error(msg) + return imported diff --git a/robot/lib/python3.8/site-packages/robot/output/logger.py b/robot/lib/python3.8/site-packages/robot/output/logger.py new file mode 100644 index 0000000000000000000000000000000000000000..3a392d0d68bbd3a559de7df8ab60207bc4edf7e5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/logger.py @@ -0,0 +1,250 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from contextlib import contextmanager +import os + +from robot.errors import DataError + +from .console import ConsoleOutput +from .filelogger import FileLogger +from .loggerhelper import AbstractLogger, AbstractLoggerProxy +from .stdoutlogsplitter import StdoutLogSplitter + + +class Logger(AbstractLogger): + """A global logger proxy to delegating messages to registered loggers. + + Whenever something is written to LOGGER in code, all registered loggers are + notified. Messages are also cached and cached messages written to new + loggers when they are registered. + + NOTE: This API is likely to change in future versions. + """ + + def __init__(self, register_console_logger=True): + self._console_logger = None + self._syslog = None + self._xml_logger = None + self._listeners = None + self._library_listeners = None + self._other_loggers = [] + self._message_cache = [] + self._log_message_cache = None + self._started_keywords = 0 + self._error_occurred = False + self._error_listener = None + self._prev_log_message_handlers = [] + self._enabled = 0 + self._cache_only = False + if register_console_logger: + self.register_console_logger() + + @property + def start_loggers(self): + loggers = [self._console_logger, self._syslog, self._xml_logger, + self._listeners, self._library_listeners] + return [logger for logger in self._other_loggers + loggers if logger] + + @property + def end_loggers(self): + loggers = [self._listeners, self._library_listeners, + self._console_logger, self._syslog, self._xml_logger] + return [logger for logger in loggers + self._other_loggers if logger] + + def __iter__(self): + return iter(self.end_loggers) + + def __enter__(self): + if not self._enabled: + self.register_syslog() + self._enabled += 1 + + def __exit__(self, *exc_info): + self._enabled -= 1 + if not self._enabled: + self.close() + + def register_console_logger(self, type='verbose', width=78, colors='AUTO', + markers='AUTO', stdout=None, stderr=None): + logger = ConsoleOutput(type, width, colors, markers, stdout, stderr) + self._console_logger = self._wrap_and_relay(logger) + + def _wrap_and_relay(self, logger): + logger = LoggerProxy(logger) + self._relay_cached_messages(logger) + return logger + + def _relay_cached_messages(self, logger): + if self._message_cache: + for msg in self._message_cache[:]: + logger.message(msg) + + def unregister_console_logger(self): + self._console_logger = None + + def register_syslog(self, path=None, level='INFO'): + if not path: + path = os.environ.get('ROBOT_SYSLOG_FILE', 'NONE') + level = os.environ.get('ROBOT_SYSLOG_LEVEL', level) + if path.upper() == 'NONE': + return + try: + syslog = FileLogger(path, level) + except DataError as err: + self.error("Opening syslog file '%s' failed: %s" % (path, err.message)) + else: + self._syslog = self._wrap_and_relay(syslog) + + def register_xml_logger(self, logger): + self._xml_logger = self._wrap_and_relay(logger) + + def unregister_xml_logger(self): + self._xml_logger = None + + def register_listeners(self, listeners, library_listeners): + self._listeners = listeners + self._library_listeners = library_listeners + if listeners: + self._relay_cached_messages(listeners) + + def register_logger(self, *loggers): + for logger in loggers: + logger = self._wrap_and_relay(logger) + self._other_loggers.append(logger) + + def unregister_logger(self, *loggers): + for logger in loggers: + self._other_loggers = [proxy for proxy in self._other_loggers + if proxy.logger is not logger] + + def disable_message_cache(self): + self._message_cache = None + + def register_error_listener(self, listener): + self._error_listener = listener + if self._error_occurred: + listener() + + def message(self, msg): + """Messages about what the framework is doing, warnings, errors, ...""" + if not self._cache_only: + for logger in self: + logger.message(msg) + if self._message_cache is not None: + self._message_cache.append(msg) + if msg.level == 'ERROR': + self._error_occurred = True + if self._error_listener: + self._error_listener() + + @property + @contextmanager + def cache_only(self): + self._cache_only = True + try: + yield + finally: + self._cache_only = False + + @property + @contextmanager + def delayed_logging(self): + prev_cache = self._log_message_cache + self._log_message_cache = [] + try: + yield + finally: + messages = self._log_message_cache + self._log_message_cache = prev_cache + for msg in messages or (): + self._log_message(msg, no_cache=True) + + def _log_message(self, msg, no_cache=False): + """Log messages written (mainly) by libraries.""" + if self._log_message_cache is not None and not no_cache: + msg.resolve_delayed_message() + self._log_message_cache.append(msg) + return + for logger in self: + logger.log_message(msg) + if msg.level in ('WARN', 'ERROR'): + self.message(msg) + + log_message = message + + def log_output(self, output): + for msg in StdoutLogSplitter(output): + self.log_message(msg) + + def enable_library_import_logging(self): + self._prev_log_message_handlers.append(self.log_message) + self.log_message = self.message + + def disable_library_import_logging(self): + self.log_message = self._prev_log_message_handlers.pop() + + def start_suite(self, suite): + for logger in self.start_loggers: + logger.start_suite(suite) + + def end_suite(self, suite): + for logger in self.end_loggers: + logger.end_suite(suite) + + def start_test(self, test): + for logger in self.start_loggers: + logger.start_test(test) + + def end_test(self, test): + for logger in self.end_loggers: + logger.end_test(test) + + def start_keyword(self, keyword): + # TODO: Could _prev_log_message_handlers be used also here? + self._started_keywords += 1 + self.log_message = self._log_message + for logger in self.start_loggers: + logger.start_keyword(keyword) + + def end_keyword(self, keyword): + self._started_keywords -= 1 + for logger in self.end_loggers: + logger.end_keyword(keyword) + if not self._started_keywords: + self.log_message = self.message + + def imported(self, import_type, name, **attrs): + for logger in self: + logger.imported(import_type, name, attrs) + + def output_file(self, file_type, path): + """Finished output, report, log, debug, or xunit file""" + for logger in self: + logger.output_file(file_type, path) + + def close(self): + for logger in self: + logger.close() + self.__init__(register_console_logger=False) + + +class LoggerProxy(AbstractLoggerProxy): + _methods = ('start_suite', 'end_suite', 'start_test', 'end_test', + 'start_keyword', 'end_keyword', 'message', 'log_message', + 'imported', 'output_file', 'close') + + +LOGGER = Logger() diff --git a/robot/lib/python3.8/site-packages/robot/output/loggerhelper.py b/robot/lib/python3.8/site-packages/robot/output/loggerhelper.py new file mode 100644 index 0000000000000000000000000000000000000000..e47a850a9bbe26f18465e9a685fd8f1c0ba1b3b5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/loggerhelper.py @@ -0,0 +1,153 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.model import Message as BaseMessage +from robot.utils import get_timestamp, is_unicode, unic + + +LEVELS = { + 'NONE' : 6, + 'FAIL' : 5, + 'ERROR' : 4, + 'WARN' : 3, + 'INFO' : 2, + 'DEBUG' : 1, + 'TRACE' : 0, +} + + +class AbstractLogger(object): + + def __init__(self, level='TRACE'): + self._is_logged = IsLogged(level) + + def set_level(self, level): + return self._is_logged.set_level(level) + + def trace(self, msg): + self.write(msg, 'TRACE') + + def debug(self, msg): + self.write(msg, 'DEBUG') + + def info(self, msg): + self.write(msg, 'INFO') + + def warn(self, msg): + self.write(msg, 'WARN') + + def fail(self, msg): + html = False + if msg.startswith("*HTML*"): + html = True + msg = msg[6:].lstrip() + self.write(msg, 'FAIL', html) + + def error(self, msg): + self.write(msg, 'ERROR') + + def write(self, message, level, html=False): + self.message(Message(message, level, html)) + + def message(self, msg): + raise NotImplementedError(self.__class__) + + +class Message(BaseMessage): + __slots__ = ['_message'] + + def __init__(self, message, level='INFO', html=False, timestamp=None): + message = self._normalize_message(message) + level, html = self._get_level_and_html(level, html) + timestamp = timestamp or get_timestamp() + BaseMessage.__init__(self, message, level, html, timestamp) + + def _normalize_message(self, msg): + if callable(msg): + return msg + if not is_unicode(msg): + msg = unic(msg) + if '\r\n' in msg: + msg = msg.replace('\r\n', '\n') + return msg + + def _get_level_and_html(self, level, html): + level = level.upper() + if level == 'HTML': + return 'INFO', True + if level not in LEVELS: + raise DataError("Invalid log level '%s'." % level) + return level, html + + @property + def message(self): + self.resolve_delayed_message() + return self._message + + @message.setter + def message(self, message): + self._message = message + + def resolve_delayed_message(self): + if callable(self._message): + self._message = self._message() + + +class IsLogged(object): + + def __init__(self, level): + self._str_level = level + self._int_level = self._level_to_int(level) + + def __call__(self, level): + return self._level_to_int(level) >= self._int_level + + def set_level(self, level): + old = self._str_level.upper() + self.__init__(level) + return old + + def _level_to_int(self, level): + try: + return LEVELS[level.upper()] + except KeyError: + raise DataError("Invalid log level '%s'." % level) + + +class AbstractLoggerProxy(object): + _methods = None + _no_method = lambda *args: None + + def __init__(self, logger, method_names=None, prefix=None): + self.logger = logger + for name in method_names or self._methods: + setattr(self, name, self._get_method(logger, name, prefix)) + + def _get_method(self, logger, name, prefix): + for method_name in self._get_method_names(name, prefix): + if hasattr(logger, method_name): + return getattr(logger, method_name) + return self._no_method + + def _get_method_names(self, name, prefix): + names = [name, self._toCamelCase(name)] if '_' in name else [name] + if prefix: + names += [prefix + name for name in names] + return names + + def _toCamelCase(self, name): + parts = name.split('_') + return ''.join([parts[0]] + [part.capitalize() for part in parts[1:]]) diff --git a/robot/lib/python3.8/site-packages/robot/output/output.py b/robot/lib/python3.8/site-packages/robot/output/output.py new file mode 100644 index 0000000000000000000000000000000000000000..88a796e1e2b37f99e9c6bb80a7d8343c166175f9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/output.py @@ -0,0 +1,75 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import pyloggingconf +from .debugfile import DebugFile +from .listeners import LibraryListeners, Listeners +from .logger import LOGGER +from .loggerhelper import AbstractLogger +from .xmllogger import XmlLogger + + +class Output(AbstractLogger): + + def __init__(self, settings): + AbstractLogger.__init__(self) + self._xmllogger = XmlLogger(settings.output, settings.log_level, + settings.rpa) + self.listeners = Listeners(settings.listeners, settings.log_level) + self.library_listeners = LibraryListeners(settings.log_level) + self._register_loggers(DebugFile(settings.debug_file)) + self._settings = settings + + def _register_loggers(self, debug_file): + LOGGER.register_xml_logger(self._xmllogger) + LOGGER.register_listeners(self.listeners or None, self.library_listeners) + if debug_file: + LOGGER.register_logger(debug_file) + + def register_error_listener(self, listener): + LOGGER.register_error_listener(listener) + + def close(self, result): + self._xmllogger.visit_statistics(result.statistics) + self._xmllogger.close() + LOGGER.unregister_xml_logger() + LOGGER.output_file('Output', self._settings['Output']) + + def start_suite(self, suite): + LOGGER.start_suite(suite) + + def end_suite(self, suite): + LOGGER.end_suite(suite) + + def start_test(self, test): + LOGGER.start_test(test) + + def end_test(self, test): + LOGGER.end_test(test) + + def start_keyword(self, kw): + LOGGER.start_keyword(kw) + + def end_keyword(self, kw): + LOGGER.end_keyword(kw) + + def message(self, msg): + LOGGER.log_message(msg) + + def set_log_level(self, level): + pyloggingconf.set_level(level) + self.listeners.set_log_level(level) + self.library_listeners.set_log_level(level) + return self._xmllogger.set_log_level(level) diff --git a/robot/lib/python3.8/site-packages/robot/output/pyloggingconf.py b/robot/lib/python3.8/site-packages/robot/output/pyloggingconf.py new file mode 100644 index 0000000000000000000000000000000000000000..be741b4ae1d8003ff913b18c483052eb06d5714c --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/pyloggingconf.py @@ -0,0 +1,84 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from contextlib import contextmanager +import logging + +from robot.utils import get_error_details, unic + +from . import librarylogger + + +LEVELS = {'TRACE': logging.NOTSET, + 'DEBUG': logging.DEBUG, + 'INFO': logging.INFO, + 'WARN': logging.WARNING, + 'ERROR': logging.ERROR} + + +@contextmanager +def robot_handler_enabled(level): + root = logging.getLogger() + if any(isinstance(h, RobotHandler) for h in root.handlers): + yield + return + handler = RobotHandler() + old_raise = logging.raiseExceptions + root.addHandler(handler) + logging.raiseExceptions = False + set_level(level) + try: + yield + finally: + root.removeHandler(handler) + logging.raiseExceptions = old_raise + + +def set_level(level): + try: + level = LEVELS[level.upper()] + except KeyError: + return + logging.getLogger().setLevel(level) + + +class RobotHandler(logging.Handler): + + def emit(self, record): + message, error = self._get_message(record) + method = self._get_logger_method(record.levelno) + method(message) + if error: + librarylogger.debug(error) + + def _get_message(self, record): + try: + return record.getMessage(), None + except: + message = 'Failed to log following message properly: %s' \ + % unic(record.msg) + error = '\n'.join(get_error_details()) + return message, error + + def _get_logger_method(self, level): + if level >= logging.ERROR: + return librarylogger.error + if level >= logging.WARNING: + return librarylogger.warn + if level >= logging.INFO: + return librarylogger.info + if level >= logging.DEBUG: + return librarylogger.debug + return librarylogger.trace diff --git a/robot/lib/python3.8/site-packages/robot/output/stdoutlogsplitter.py b/robot/lib/python3.8/site-packages/robot/output/stdoutlogsplitter.py new file mode 100644 index 0000000000000000000000000000000000000000..b442a37084042d04fb4fbb7ff7cf5645731e4e58 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/stdoutlogsplitter.py @@ -0,0 +1,58 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +from robot.utils import format_time + +from .loggerhelper import Message + + +class StdoutLogSplitter(object): + """Splits messages logged through stdout (or stderr) into Message objects""" + + _split_from_levels = re.compile('^(?:\*' + '(TRACE|DEBUG|INFO|HTML|WARN|ERROR)' + '(:\d+(?:\.\d+)?)?' # Optional timestamp + '\*)', re.MULTILINE) + + def __init__(self, output): + self._messages = list(self._get_messages(output.strip())) + + def _get_messages(self, output): + for level, timestamp, msg in self._split_output(output): + if timestamp: + timestamp = self._format_timestamp(timestamp[1:]) + yield Message(msg.strip(), level, timestamp=timestamp) + + def _split_output(self, output): + tokens = self._split_from_levels.split(output) + tokens = self._add_initial_level_and_time_if_needed(tokens) + for i in range(0, len(tokens), 3): + yield tokens[i:i+3] + + def _add_initial_level_and_time_if_needed(self, tokens): + if self._output_started_with_level(tokens): + return tokens[1:] + return ['INFO', None] + tokens + + def _output_started_with_level(self, tokens): + return tokens[0] == '' + + def _format_timestamp(self, millis): + return format_time(float(millis)/1000, millissep='.') + + def __iter__(self): + return iter(self._messages) diff --git a/robot/lib/python3.8/site-packages/robot/output/xmllogger.py b/robot/lib/python3.8/site-packages/robot/output/xmllogger.py new file mode 100644 index 0000000000000000000000000000000000000000..25545f0206ebb25799fc8c8ad7bf3b4235236698 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/output/xmllogger.py @@ -0,0 +1,157 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import XmlWriter, NullMarkupWriter, get_timestamp, unic +from robot.version import get_full_version +from robot.result.visitor import ResultVisitor + +from .loggerhelper import IsLogged + + +class XmlLogger(ResultVisitor): + + def __init__(self, path, log_level='TRACE', rpa=False, generator='Robot'): + self._log_message_is_logged = IsLogged(log_level) + self._error_message_is_logged = IsLogged('WARN') + self._writer = self._get_writer(path, rpa, generator) + self._errors = [] + + def _get_writer(self, path, rpa, generator): + if not path: + return NullMarkupWriter() + writer = XmlWriter(path, write_empty=False, usage='output') + writer.start('robot', {'generator': get_full_version(generator), + 'generated': get_timestamp(), + 'rpa': 'true' if rpa else 'false'}) + return writer + + def close(self): + self.start_errors() + for msg in self._errors: + self._write_message(msg) + self.end_errors() + self._writer.end('robot') + self._writer.close() + + def set_log_level(self, level): + return self._log_message_is_logged.set_level(level) + + def message(self, msg): + if self._error_message_is_logged(msg.level): + self._errors.append(msg) + + def log_message(self, msg): + if self._log_message_is_logged(msg.level): + self._write_message(msg) + + def _write_message(self, msg): + attrs = {'timestamp': msg.timestamp or 'N/A', 'level': msg.level} + if msg.html: + attrs['html'] = 'yes' + self._writer.element('msg', msg.message, attrs) + + def start_keyword(self, kw): + attrs = {'name': kw.kwname, 'library': kw.libname} + if kw.type != 'kw': + attrs['type'] = kw.type + self._writer.start('kw', attrs) + self._write_list('tags', 'tag', [unic(t) for t in kw.tags]) + self._writer.element('doc', kw.doc) + self._write_list('arguments', 'arg', [unic(a) for a in kw.args]) + self._write_list('assign', 'var', kw.assign) + + def end_keyword(self, kw): + if kw.timeout: + self._writer.element('timeout', attrs={'value': unic(kw.timeout)}) + self._write_status(kw) + self._writer.end('kw') + + def start_test(self, test): + self._writer.start('test', {'id': test.id, 'name': test.name}) + + def end_test(self, test): + self._writer.element('doc', test.doc) + self._write_list('tags', 'tag', test.tags) + if test.timeout: + self._writer.element('timeout', attrs={'value': unic(test.timeout)}) + self._write_status(test, {'critical': 'yes' if test.critical else 'no'}) + self._writer.end('test') + + def start_suite(self, suite): + attrs = {'id': suite.id, 'name': suite.name, 'source': suite.source} + self._writer.start('suite', attrs) + + def end_suite(self, suite): + self._writer.element('doc', suite.doc) + if suite.metadata: + self._write_metadata(suite.metadata) + self._write_status(suite) + self._writer.end('suite') + + def _write_metadata(self, metadata): + self._writer.start('metadata') + for name, value in metadata.items(): + self._writer.element('item', value, {'name': name}) + self._writer.end('metadata') + + def start_statistics(self, stats): + self._writer.start('statistics') + + def end_statistics(self, stats): + self._writer.end('statistics') + + def start_total_statistics(self, total_stats): + self._writer.start('total') + + def end_total_statistics(self, total_stats): + self._writer.end('total') + + def start_tag_statistics(self, tag_stats): + self._writer.start('tag') + + def end_tag_statistics(self, tag_stats): + self._writer.end('tag') + + def start_suite_statistics(self, tag_stats): + self._writer.start('suite') + + def end_suite_statistics(self, tag_stats): + self._writer.end('suite') + + def visit_stat(self, stat): + self._writer.element('stat', stat.name, + stat.get_attributes(values_as_strings=True)) + + def start_errors(self, errors=None): + self._writer.start('errors') + + def end_errors(self, errors=None): + self._writer.end('errors') + + def _write_list(self, container_tag, item_tag, items): + if items: + self._writer.start(container_tag) + for item in items: + self._writer.element(item_tag, item) + self._writer.end(container_tag) + + def _write_status(self, item, extra_attrs=None): + attrs = {'status': item.status, 'starttime': item.starttime or 'N/A', + 'endtime': item.endtime or 'N/A'} + if not (item.starttime and item.endtime): + attrs['elapsedtime'] = str(item.elapsedtime) + if extra_attrs: + attrs.update(extra_attrs) + self._writer.element('status', item.message, attrs) diff --git a/robot/lib/python3.8/site-packages/robot/parsing/__init__.py b/robot/lib/python3.8/site-packages/robot/parsing/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3015a069dec3452785c44ce1e4f63d77e110c1c9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/__init__.py @@ -0,0 +1,382 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +r"""Module implementing test data parsing. + +Exposed API +----------- + +The publicly exposed parsing entry points are the following: + +* :func:`~.lexer.lexer.get_tokens`, + :func:`~.lexer.lexer.get_resource_tokens`, and + :func:`~.lexer.lexer.get_init_tokens` functions for tokenizing data. + +* :class:`~.lexer.tokens.Token` class that contains all token types as + class attributes. + +* :func:`~.parser.parser.get_model`, + :func:`~.parser.parser.get_resource_model`, and + :func:`~.parser.parser.get_init_model` functions for getting a higher + level model represented as an abstract syntax tree (AST). + +.. tip:: Like with rest of the public API, these functions and classes are + exposed also via the :mod:`robot.api` package. When they are used + by external code, it is recommended they are imported like + ``from robot.api import get_tokens``. + +.. note:: The :mod:`robot.parsing` package has been totally rewritten in + Robot Framework 3.2 and all code using it needs to be updated. + Depending on the use case, it may be possible to instead use the + higher level :func:`~robot.running.builder.builders.TestSuiteBuilder` + that has only seen minor configuration changes. + +Parsing data to tokens +---------------------- + +Data can be parsed to tokens by using +:func:`~.lexer.lexer.get_tokens`, +:func:`~.lexer.lexer.get_resource_tokens` or +:func:`~.lexer.lexer.get_init_tokens` functions depending on does the data +represent a test case (or task) file, a resource file, or a suite +initialization file. In practice the difference between these functions is +what settings and sections are valid. + +Typically the data is easier to inspect and modify by using the higher level +model discussed in the next section, but in some cases the token stream can +be enough. Tokens returned by the aforementioned functions are +:class:`~.lexer.tokens.Token` instances and they have the token type, value, +and position easily available as their attributes. Tokens also have useful +string representation used by the example below:: + + from robot.api import get_tokens + + path = 'example.robot' + + for token in get_tokens(path): + print(repr(token)) + +If the :file:`example.robot` used by the above example would contain + +.. code-block:: robotframework + + *** Test Cases *** + Example + Keyword argument + + Second example + Keyword xxx + + *** Keywords *** + Keyword + [Arguments] ${arg} + Log ${arg} + +then the beginning of the output got when running the earlier code would +look like this:: + + Token(TESTCASE_HEADER, '*** Test Cases ***', 1, 0) + Token(EOL, '\n', 1, 18) + Token(EOS, '', 1, 19) + Token(TESTCASE_NAME, 'Example', 2, 0) + Token(EOL, '\n', 2, 7) + Token(EOS, '', 2, 8) + Token(SEPARATOR, ' ', 3, 0) + Token(KEYWORD, 'Keyword', 3, 4) + Token(SEPARATOR, ' ', 3, 11) + Token(ARGUMENT, 'argument', 3, 15) + Token(EOL, '\n', 3, 23) + Token(EOS, '', 3, 24) + Token(EOL, '\n', 4, 0) + Token(EOS, '', 4, 1) + +The output shows token type, value, line number and column offset. The +``EOL`` tokens denote end of a line and they include the new line character +and possible trailing spaces. The ``EOS`` tokens denote end of a logical +statement. Typically a single line forms a statement, but when the ``...`` +syntax is used for continuation, a statement spans multiple lines. In +special cases a single line can also contain multiple statements. + +See the documentation of :func:`~.lexer.lexer.get_tokens` for details +about different ways how to specify the data to be parsed, how to control +should all tokens or only data tokens be returned, and should variables in +keyword arguments and elsewhere be tokenized or not. + +Parsing data to model +--------------------- + +Data can be parsed to a higher level model by using +:func:`~.parser.parser.get_model`, +:func:`~.parser.parser.get_resource_model`, or +:func:`~.parser.parser.get_init_model` functions depending on the data +type same way as when `parsing data to tokens`_. + +The model is represented as an abstract syntax tree (AST) implemented on top +of Python's standard `ast.AST`_ class. The ast_ module can also be used +for inspecting and modifying the module. Most importantly, `ast.NodeVisitor`_ +and `ast.NodeTransformer`_ ease traversing the model as explained in the +sections below. The `ast.dump()`_ function, or the third-party astpretty_ +module, can be used for debugging:: + + import ast + import astpretty # third-party module + from robot.api import get_model + + model = get_model('example.robot') + print(ast.dump(model)) + print('-' * 72) + astpretty.pprint(model) + +Running this code with the :file:`example.robot` file from the previous +section would produce so much output that it is not included here. If +you are going to work with Robot Framework's AST, you are recommended to +try this on your own. + +The model is build from blocks like +:class:`~.model.blocks.File` (the root of the model), +:class:`~.model.blocks.TestCaseSection`, and +:class:`~.model.blocks.TestCase` +implemented in the :mod:`~.model.blocks` module and from statements like +:class:`~.model.statements.TestCaseSectionHeader`, +:class:`~.model.statements.Documentation`, and +:class:`~.model.statements.KeywordCall` +implemented in the :mod:`~.model.statements` module. +Both blocks and statements are AST nodes based on `ast.AST`_. +Blocks can contain other blocks and statements as child nodes whereas +statements have only tokens. These tokens contain the actual data +represented as :class:`~.lexer.tokens.Token` instances. + +.. _ast: https://docs.python.org/library/ast.html +.. _ast.AST: https://docs.python.org/library/ast.html#ast.AST +.. _ast.NodeVisitor: https://docs.python.org/library/ast.html#ast.NodeVisitor +.. _ast.NodeTransformer: https://docs.python.org/library/ast.html#ast.NodeTransformer +.. _ast.dump(): https://docs.python.org/library/ast.html#ast.dump +.. _astpretty: https://pypi.org/project/astpretty + +Inspecting model +'''''''''''''''' + +The easiest way to inspect what data a model contains is implementing +a visitor based on `ast.NodeVisitor`_ and implementing ``visit_NodeName`` +methods as needed. The following example illustrates how to find what tests +a certain test case file contains:: + + import ast + from robot.api import get_model + + + class TestNamePrinter(ast.NodeVisitor): + + def visit_File(self, node): + print(f"File '{node.source}' has following tests:") + # Must call `generic_visit` to visit also child nodes. + self.generic_visit(node) + + def visit_TestCaseName(self, node): + print(f"- {node.name} (on line {node.lineno})") + + + model = get_model('example.robot') + printer = TestNamePrinter() + printer.visit(model) + +When the above code is run using the earlier :file:`example.robot`, the +output is this:: + + File 'example.robot' has following tests: + - Example (on line 2) + - Second example (on line 5) + +Modifying token values +'''''''''''''''''''''' + +The model can be modified simply by modifying token values. If changes need +to be saved, that is as easy as calling the :meth:`~.model.blocks.File.save` +method of the root model object. When just modifying token values, it is +possible to still extend `ast.NodeVisitor`_. The next section discusses +adding or removing nodes and then `ast.NodeTransformer`_ should be used +instead. + +Modifications to tokens obviously require finding the tokens to be modified. +The first step is finding statements containing the tokens by implementing +needed ``visit_StatementName`` methods. Then the exact token or tokens +can be found using node's +:meth:`~.model.statements.Statement.get_token` or +:meth:`~.model.statements.Statement.get_tokens` methods. +If only token values are needed, +:meth:`~.model.statements.Statement.get_value` or +:meth:`~.model.statements.Statement.get_values` can be used as a shortcut. +First finding statements and then the right tokens is illustrated by +this example that renames keywords:: + + import ast + from robot.api import get_model, Token + + + class KeywordRenamer(ast.NodeVisitor): + + def __init__(self, old_name, new_name): + self.old_name = self.normalize(old_name) + self.new_name = new_name + + def normalize(self, name): + return name.lower().replace(' ', '').replace('_', '') + + def visit_KeywordName(self, node): + # Rename keyword definitions. + if self.normalize(node.name) == self.old_name: + token = node.get_token(Token.KEYWORD_NAME) + token.value = self.new_name + + def visit_KeywordCall(self, node): + # Rename keyword usages. + if self.normalize(node.keyword) == self.old_name: + token = node.get_token(Token.KEYWORD) + token.value = self.new_name + + + model = get_model('example.robot') + renamer = KeywordRenamer('Keyword', 'New Name') + renamer.visit(model) + model.save() + +If you run the above example using the earlier :file:`example.robot`, you +can see that the ``Keyword`` keyword has been renamed to ``New Name``. Notice +that a real keyword renamer needed to take into account also keywords used +with setups, teardowns and templates. + +When token values are changed, column offset of the other tokens on same +line are likely to be wrong. This does not affect saving the model or other +typical usages, but if it is a problem then the caller needs to updated +offsets separately. + +Adding and removing nodes +------------------------- + +Bigger changes to model are somewhat more complicated than just modifying +existing token values. When doing this kind of changes, `ast.NodeTransformer`_ +needs to be used instead of `ast.NodeVisitor`_ that was used in earlier +examples. + +Removing nodes is relative easy and is accomplished by returning ``None`` +from ``visit_NodeName`` methods. Remember to return the original node, +or possibly a replacement node, from all of these methods when you do not +want a node to be removed. + +Adding nodes is unfortunately not supported by the public :mod:`robot.api` +interface and the needed block and statement nodes need to be imported +via the :mod:`robot.parsing.model` package. That package is considered +private and may change in the future. A stable public API can be added, +and functionality related to adding nodes improved in general, if there +are concrete needs for this kind of advanced usage. + +The following example demonstrates both removing and adding nodes. +If you run it against the earlier :file:`example.robot`, you see that +the first test gets a new keyword, the second test is removed, and +settings section with documentation is added. + +:: + + import ast + from robot.api import get_model, Token + from robot.parsing.model import SettingSection, Statement + + + class TestModifier(ast.NodeTransformer): + + def visit_TestCase(self, node): + # The matched `TestCase` node is a block with `header` and `body` + # attributes. `header` is a statement with familiar `get_token` and + # `get_value` methods for getting certain tokens or their value. + name = node.header.get_value(Token.TESTCASE_NAME) + # Returning `None` drops the node altogether i.e. removes this test. + if name == 'Second example': + return None + # Construct new keyword call statement from tokens. + new_keyword = Statement.from_tokens([ + Token(Token.SEPARATOR, ' '), + Token(Token.KEYWORD, 'New Keyword'), + Token(Token.SEPARATOR, ' '), + Token(Token.ARGUMENT, 'xxx'), + Token(Token.EOL, '\n') + ]) + # Add the keyword call to test as the second item. `body` is a list. + node.body.insert(1, new_keyword) + # No need to call `generic_visit` because we are not modifying child + # nodes. The node itself must to be returned to avoid dropping it. + return node + + def visit_File(self, node): + # Create settings section with documentation. + setting_header = Statement.from_tokens([ + Token(Token.SETTING_HEADER, '*** Settings ***'), + Token(Token.EOL, '\n') + ]) + documentation = Statement.from_tokens([ + Token(Token.DOCUMENTATION, 'Documentation'), + Token(Token.SEPARATOR, ' '), + Token(Token.ARGUMENT, 'This is getting pretty advanced'), + Token(Token.EOL, '\n'), + Token(Token.CONTINUATION, '...'), + Token(Token.SEPARATOR, ' '), + Token(Token.ARGUMENT, 'and this API definitely could be better.'), + Token(Token.EOL, '\n') + ]) + empty_line = Statement.from_tokens([ + Token(Token.EOL, '\n') + ]) + body = [documentation, empty_line] + settings = SettingSection(setting_header, body) + # Add settings to the beginning of the file. + node.sections.insert(0, settings) + # Must call `generic_visit` to visit also child nodes. + return self.generic_visit(node) + + + model = get_model('example.robot') + modifier = TestModifier() + modifier.visit(model) + model.save() + +Executing model +--------------- + +It is possible to convert a parsed and possibly modified model into an +executable :class:`~robot.running.model.TestSuite` structure by using its +:func:`~robot.running.model.TestSuite.from_model` class method. In this case +the :func:`~.parser.parser.get_model` function should be given the ``curdir`` +argument to get possible ``${CURDIR}`` variable resolved correctly. + +:: + + from robot.api import get_model, TestSuite + + model = get_model('example.robot', curdir='/home/robot/example') + # modify model as needed + suite = TestSuite.from_model(model) + suite.run() + +For more details about executing the created +:class:`~robot.running.model.TestSuite` object, see the documentation +of its :meth:`~robot.running.model.TestSuite.run` method. Notice also +that if you do not need to modify the parsed model, it is easier to +get the executable suite by using the +:func:`~robot.running.model.TestSuite.from_file_system` class method. +""" + +from .lexer import get_tokens, get_resource_tokens, get_init_tokens, Token +from .model import ModelTransformer, ModelVisitor +from .parser import get_model, get_resource_model, get_init_model +from .suitestructure import SuiteStructureBuilder, SuiteStructureVisitor diff --git a/robot/lib/python3.8/site-packages/robot/parsing/lexer/__init__.py b/robot/lib/python3.8/site-packages/robot/parsing/lexer/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a9c47f0d0d5f80cf103bddfdd9752b895cabc135 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/lexer/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .lexer import get_tokens, get_resource_tokens, get_init_tokens +from .tokens import Token diff --git a/robot/lib/python3.8/site-packages/robot/parsing/lexer/blocklexers.py b/robot/lib/python3.8/site-packages/robot/parsing/lexer/blocklexers.py new file mode 100644 index 0000000000000000000000000000000000000000..96f2881212a3da1449f4c169c16d823b5220d019 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/lexer/blocklexers.py @@ -0,0 +1,222 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .tokens import Token +from .statementlexers import (Lexer, + SettingSectionHeaderLexer, SettingLexer, + VariableSectionHeaderLexer, VariableLexer, + TestCaseSectionHeaderLexer, + KeywordSectionHeaderLexer, + CommentSectionHeaderLexer, CommentLexer, + ErrorSectionHeaderLexer, + TestOrKeywordSettingLexer, + KeywordCallLexer, + ForLoopHeaderLexer, + EndLexer) + + +class BlockLexer(Lexer): + + def __init__(self, ctx): + """:type ctx: :class:`robot.parsing.lexer.context.FileContext`""" + Lexer.__init__(self, ctx) + self.lexers = [] + + def accepts_more(self, statement): + return True + + def input(self, statement): + if self.lexers and self.lexers[-1].accepts_more(statement): + lexer = self.lexers[-1] + else: + lexer = self.lexer_for(statement) + self.lexers.append(lexer) + lexer.input(statement) + return lexer + + def lexer_for(self, statement): + for cls in self.lexer_classes(): + lexer = cls(self.ctx) + if lexer.handles(statement): + return lexer + raise TypeError("%s did not find lexer for statement %s." + % (type(self).__name__, statement)) + + def lexer_classes(self): + return () + + def lex(self): + for lexer in self.lexers: + lexer.lex() + + def _lex_with_priority(self, priority): + for lexer in self.lexers: + if isinstance(lexer, priority): + lexer.lex() + for lexer in self.lexers: + if not isinstance(lexer, priority): + lexer.lex() + + +class FileLexer(BlockLexer): + + def lex(self): + self._lex_with_priority(priority=SettingSectionLexer) + + def lexer_classes(self): + return (SettingSectionLexer, VariableSectionLexer, + TestCaseSectionLexer, KeywordSectionLexer, + CommentSectionLexer, ErrorSectionLexer, + ImplicitCommentSectionLexer) + + +class SectionLexer(BlockLexer): + + def accepts_more(self, statement): + return not statement[0].value.startswith('*') + + +class SettingSectionLexer(SectionLexer): + + def handles(self, statement): + return self.ctx.setting_section(statement) + + def lexer_classes(self): + return (SettingSectionHeaderLexer, SettingLexer) + + +class VariableSectionLexer(SectionLexer): + + def handles(self, statement): + return self.ctx.variable_section(statement) + + def lexer_classes(self): + return (VariableSectionHeaderLexer, VariableLexer) + + +class TestCaseSectionLexer(SectionLexer): + + def handles(self, statement): + return self.ctx.test_case_section(statement) + + def lexer_classes(self): + return (TestCaseSectionHeaderLexer, TestCaseLexer) + + +class KeywordSectionLexer(SettingSectionLexer): + + def handles(self, statement): + return self.ctx.keyword_section(statement) + + def lexer_classes(self): + return (KeywordSectionHeaderLexer, KeywordLexer) + + +class CommentSectionLexer(SectionLexer): + + def handles(self, statement): + return self.ctx.comment_section(statement) + + def lexer_classes(self): + return (CommentSectionHeaderLexer, CommentLexer) + + +class ImplicitCommentSectionLexer(SectionLexer): + + def handles(self, statement): + return True + + def lexer_classes(self): + return (CommentLexer,) + + +class ErrorSectionLexer(SectionLexer): + + def handles(self, statement): + return statement and statement[0].value.startswith('*') + + def lexer_classes(self): + return (ErrorSectionHeaderLexer, CommentLexer) + + +class TestOrKeywordLexer(BlockLexer): + name_type = NotImplemented + _name_seen = False + + def accepts_more(self, statement): + return not statement[0].value + + def input(self, statement): + self._handle_name_or_indentation(statement) + if statement: + BlockLexer.input(self, statement) + + def _handle_name_or_indentation(self, statement): + if not self._name_seen: + statement.pop(0).type = self.name_type + self._name_seen = True + else: + while statement and not statement[0].value: + statement.pop(0).type = None # These tokens will be ignored + + def lexer_classes(self): + return (TestOrKeywordSettingLexer, ForLoopLexer, KeywordCallLexer) + + +class TestCaseLexer(TestOrKeywordLexer): + name_type = Token.TESTCASE_NAME + + def __init__(self, ctx): + TestOrKeywordLexer.__init__(self, ctx.test_case_context()) + + def lex(self,): + self._lex_with_priority(priority=TestOrKeywordSettingLexer) + + +class KeywordLexer(TestOrKeywordLexer): + name_type = Token.KEYWORD_NAME + + def __init__(self, ctx): + TestOrKeywordLexer.__init__(self, ctx.keyword_context()) + + +class ForLoopLexer(BlockLexer): + + def __init__(self, ctx): + BlockLexer.__init__(self, ctx) + self._old_style_for = False + self._end_seen = False + + def handles(self, statement): + return ForLoopHeaderLexer(self.ctx).handles(statement) + + def accepts_more(self, statement): + if statement[0].value == '\\': + statement[0].type = Token.OLD_FOR_INDENT + self._old_style_for = True + return True + elif self._old_style_for: + return EndLexer(self.ctx).handles(statement) + return not self._end_seen + + def input(self, statement): + lexer = BlockLexer.input(self, statement) + if isinstance(lexer, EndLexer): + self._end_seen = True + elif statement[0].type == Token.OLD_FOR_INDENT: + statement.pop(0) + + def lexer_classes(self): + return (ForLoopHeaderLexer, EndLexer, KeywordCallLexer) diff --git a/robot/lib/python3.8/site-packages/robot/parsing/lexer/context.py b/robot/lib/python3.8/site-packages/robot/parsing/lexer/context.py new file mode 100644 index 0000000000000000000000000000000000000000..60f373ffd43eba30e3656168cf44eab50a4c91b8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/lexer/context.py @@ -0,0 +1,90 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .sections import (InitFileSections, TestCaseFileSections, + ResourceFileSections) +from .settings import (InitFileSettings, TestCaseFileSettings, + ResourceFileSettings, TestCaseSettings, KeywordSettings) + + +class LexingContext(object): + settings_class = None + + def __init__(self, settings=None): + self.settings = settings or self.settings_class() + + def lex_setting(self, statement): + self.settings.lex(statement) + + +class FileContext(LexingContext): + sections_class = None + + def __init__(self, settings=None): + LexingContext.__init__(self, settings) + self.sections = self.sections_class() + + def setting_section(self, statement): + return self.sections.setting(statement) + + def variable_section(self, statement): + return self.sections.variable(statement) + + def test_case_section(self, statement): + return self.sections.test_case(statement) + + def keyword_section(self, statement): + return self.sections.keyword(statement) + + def comment_section(self, statement): + return self.sections.comment(statement) + + def keyword_context(self): + return KeywordContext(settings=KeywordSettings()) + + def lex_invalid_section(self, statement): + self.sections.lex_invalid(statement) + + +class TestCaseFileContext(FileContext): + sections_class = TestCaseFileSections + settings_class = TestCaseFileSettings + + def test_case_context(self): + return TestCaseContext(settings=TestCaseSettings(self.settings)) + + +class ResourceFileContext(FileContext): + sections_class = ResourceFileSections + settings_class = ResourceFileSettings + + +class InitFileContext(FileContext): + sections_class = InitFileSections + settings_class = InitFileSettings + + +class TestCaseContext(LexingContext): + + @property + def template_set(self): + return self.settings.template_set + + +class KeywordContext(LexingContext): + + @property + def template_set(self): + return False diff --git a/robot/lib/python3.8/site-packages/robot/parsing/lexer/lexer.py b/robot/lib/python3.8/site-packages/robot/parsing/lexer/lexer.py new file mode 100644 index 0000000000000000000000000000000000000000..0e6a9e64f2bbb6ac524af1c66ed9f98b7db99567 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/lexer/lexer.py @@ -0,0 +1,213 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from itertools import chain + +from robot.errors import DataError +from robot.utils import get_error_message, FileReader + +from .blocklexers import FileLexer +from .context import InitFileContext, TestCaseFileContext, ResourceFileContext +from .tokenizer import Tokenizer +from .tokens import EOS, Token + + +def get_tokens(source, data_only=False, tokenize_variables=False): + """Parses the given source to tokens. + + :param source: The source where to read the data. Can be a path to + a source file as a string or as ``pathlib.Path`` object, an already + opened file object, or Unicode text containing the date directly. + Source files must be UTF-8 encoded. + :param data_only: When ``False`` (default), returns all tokens. When set + to ``True``, omits separators, comments, continuation markers, and + other non-data tokens. + :param tokenize_variables: When ``True``, possible variables in keyword + arguments and elsewhere are tokenized. See the + :meth:`~robot.parsing.lexer.tokens.Token.tokenize_variables` + method for details. + + Returns a generator that yields :class:`~robot.parsing.lexer.tokens.Token` + instances. + """ + lexer = Lexer(TestCaseFileContext(), data_only, tokenize_variables) + lexer.input(source) + return lexer.get_tokens() + + +def get_resource_tokens(source, data_only=False, tokenize_variables=False): + """Parses the given source to resource file tokens. + + Otherwise same as :func:`get_tokens` but the source is considered to be + a resource file. This affects, for example, what settings are valid. + """ + lexer = Lexer(ResourceFileContext(), data_only, tokenize_variables) + lexer.input(source) + return lexer.get_tokens() + + +def get_init_tokens(source, data_only=False, tokenize_variables=False): + """Parses the given source to init file tokens. + + Otherwise same as :func:`get_tokens` but the source is considered to be + a suite initialization file. This affects, for example, what settings are + valid. + """ + lexer = Lexer(InitFileContext(), data_only, tokenize_variables) + lexer.input(source) + return lexer.get_tokens() + + +class Lexer(object): + + def __init__(self, ctx, data_only=False, tokenize_variables=False): + self.lexer = FileLexer(ctx) + self.data_only = data_only + self.tokenize_variables = tokenize_variables + self.statements = [] + + def input(self, source): + for statement in Tokenizer().tokenize(self._read(source), + self.data_only): + # Store all tokens but pass only data tokens to lexer. + self.statements.append(statement) + if self.data_only: + data = statement[:] + else: + # Separators, comments, etc. already have type, data doesn't. + data = [t for t in statement if t.type is None] + if data: + self.lexer.input(data) + + def _read(self, source): + try: + with FileReader(source, accept_text=True) as reader: + return reader.read() + except: + raise DataError(get_error_message()) + + def get_tokens(self): + self.lexer.lex() + statements = self._handle_old_for(self.statements) + if not self.data_only: + statements = chain.from_iterable( + self._split_trailing_commented_and_empty_lines(s) + for s in statements + ) + tokens = self._get_tokens(statements) + if self.tokenize_variables: + tokens = self._tokenize_variables(tokens) + return tokens + + def _get_tokens(self, statements): + # Setting local variables is performance optimization to avoid + # unnecessary lookups and attribute access. + if self.data_only: + ignored_types = {None, Token.COMMENT_HEADER, Token.COMMENT, + Token.OLD_FOR_INDENT} + else: + ignored_types = {None} + name_types = (Token.TESTCASE_NAME, Token.KEYWORD_NAME) + separator_type = Token.SEPARATOR + eol_type = Token.EOL + for statement in statements: + name_seen = False + separator_after_name = None + prev_token = None + for token in statement: + token_type = token.type + if token_type in ignored_types: + continue + if name_seen: + if token_type == separator_type: + separator_after_name = token + continue + if token_type != eol_type: + yield EOS.from_token(prev_token) + if separator_after_name: + yield separator_after_name + name_seen = False + if token_type in name_types: + name_seen = True + prev_token = token + yield token + if prev_token: + yield EOS.from_token(prev_token) + + def _handle_old_for(self, statements): + end_statement = [Token(Token.SEPARATOR), Token(Token.END)] + old_for = False + for statement in statements: + marker = self._get_first_data_token(statement) + if marker: + if marker.type == Token.OLD_FOR_INDENT: + old_for = True + elif old_for: + if marker.type == Token.END: + # We get here if block has been indented with '\' but + # there is also 'END'. The former is deprecated and + # removing the value causes a deprecation warning. + marker.value = '' + else: + yield end_statement + old_for = False + yield statement + if old_for: + yield end_statement + + def _get_first_data_token(self, statement): + non_data_tokens = Token.NON_DATA_TOKENS + (None,) + for token in statement: + if token.type not in non_data_tokens: + return token + return None + + def _split_trailing_commented_and_empty_lines(self, statement): + lines = self._split_to_lines(statement) + commented_or_empty = [] + for line in reversed(lines): + if not self._is_commented_or_empty(line): + break + commented_or_empty.append(line) + if not commented_or_empty: + return [statement] + lines = lines[:-len(commented_or_empty)] + statement = list(chain.from_iterable(lines)) + return [statement] + list(reversed(commented_or_empty)) + + def _split_to_lines(self, statement): + lines = [] + current = [] + for token in statement: + current.append(token) + if token.type == Token.EOL: + lines.append(current) + current = [] + if current: + lines.append(current) + return lines + + def _is_commented_or_empty(self, line): + separator_or_ignore = (Token.SEPARATOR, None) + comment_or_eol = (Token.COMMENT, Token.EOL) + for token in line: + if token.type not in separator_or_ignore: + return token.type in comment_or_eol + return False + + def _tokenize_variables(self, tokens): + for token in tokens: + for t in token.tokenize_variables(): + yield t diff --git a/robot/lib/python3.8/site-packages/robot/parsing/lexer/sections.py b/robot/lib/python3.8/site-packages/robot/parsing/lexer/sections.py new file mode 100644 index 0000000000000000000000000000000000000000..56db55cf2d4b637b162b9443795b6e08fef8f061 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/lexer/sections.py @@ -0,0 +1,97 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import normalize_whitespace + +from .tokens import Token + + +class Sections(object): + setting_markers = ('Settings', 'Setting') + variable_markers = ('Variables', 'Variable') + test_case_markers = ('Test Cases', 'Test Case', 'Tasks', 'Task') + keyword_markers = ('Keywords', 'Keyword') + comment_markers = ('Comments', 'Comment') + + def setting(self, statement): + return self._handles(statement, self.setting_markers) + + def variable(self, statement): + return self._handles(statement, self.variable_markers) + + def test_case(self, statement): + return False + + def keyword(self, statement): + return self._handles(statement, self.keyword_markers) + + def comment(self, statement): + return self._handles(statement, self.comment_markers) + + def _handles(self, statement, markers): + marker = statement[0].value + return marker.startswith('*') and self._normalize(marker) in markers + + def _normalize(self, marker): + return normalize_whitespace(marker).strip('* ').title() + + def lex_invalid(self, statement): + message, fatal = self._get_invalid_section_error(statement[0].value) + statement[0].set_error(message, fatal) + for token in statement[1:]: + token.type = Token.COMMENT + + def _get_invalid_section_error(self, header): + raise NotImplementedError + + +class TestCaseFileSections(Sections): + + def test_case(self, statement): + return self._handles(statement, self.test_case_markers) + + def _get_invalid_section_error(self, header): + return ("Unrecognized section header '%s'. Valid sections: " + "'Settings', 'Variables', 'Test Cases', 'Tasks', " + "'Keywords' and 'Comments'." % header), False + + +class ResourceFileSections(Sections): + + def _get_invalid_section_error(self, header): + name = self._normalize(header) + if name in self.test_case_markers: + message = "Resource file with '%s' section is invalid." % name + fatal = True + else: + message = ("Unrecognized section header '%s'. Valid sections: " + "'Settings', 'Variables', 'Keywords' and 'Comments'." + % header) + fatal = False + return message, fatal + + +class InitFileSections(Sections): + + def _get_invalid_section_error(self, header): + name = self._normalize(header) + if name in self.test_case_markers: + message = ("'%s' section is not allowed in suite initialization " + "file." % name) + else: + message = ("Unrecognized section header '%s'. Valid sections: " + "'Settings', 'Variables', 'Keywords' and 'Comments'." + % header) + return message, False diff --git a/robot/lib/python3.8/site-packages/robot/parsing/lexer/settings.py b/robot/lib/python3.8/site-packages/robot/parsing/lexer/settings.py new file mode 100644 index 0000000000000000000000000000000000000000..8d626eb843a456e49485964863d2bc812c1dfa4e --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/lexer/settings.py @@ -0,0 +1,226 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import normalize, normalize_whitespace, RecommendationFinder + +from .tokens import Token + + +class Settings(object): + names = () + aliases = {} + multi_use = ( + 'Metadata', + 'Library', + 'Resource', + 'Variables' + ) + single_value = ( + 'Resource', + 'Test Timeout', + 'Test Template', + 'Timeout', + 'Template' + ) + name_and_arguments = ( + 'Metadata', + 'Suite Setup', + 'Suite Teardown', + 'Test Setup', + 'Test Teardown', + 'Test Template', + 'Setup', + 'Teardown', + 'Template', + 'Resource', + 'Variables' + ) + name_arguments_and_with_name = ( + 'Library', + ) + + def __init__(self): + self.settings = {n: None for n in self.names} + + def lex(self, statement): + setting = statement[0] + name = self._format_name(setting.value) + normalized = self._normalize_name(name) + try: + self._validate(name, normalized, statement) + except ValueError as err: + self._lex_error(setting, statement[1:], err.args[0]) + else: + self._lex_setting(setting, statement[1:], normalized) + + def _format_name(self, name): + return name + + def _normalize_name(self, name): + name = normalize_whitespace(name).title() + if name in self.aliases: + return self.aliases[name] + return name + + def _validate(self, name, normalized, statement): + if normalized not in self.settings: + message = self._get_non_existing_setting_message(name, normalized) + raise ValueError(message) + if self.settings[normalized] is not None and normalized not in self.multi_use: + raise ValueError("Setting '%s' is allowed only once. " + "Only the first value is used." % name) + if normalized in self.single_value and len(statement) > 2: + raise ValueError("Setting '%s' accepts only one value, got %s." + % (name, len(statement) - 1)) + + def _get_non_existing_setting_message(self, name, normalized): + if normalized in TestCaseFileSettings.names: + is_resource = isinstance(self, ResourceFileSettings) + return "Setting '%s' is not allowed in %s file." % ( + name, 'resource' if is_resource else 'suite initialization' + ) + return RecommendationFinder(normalize).find_and_format( + name=normalized, + candidates=tuple(self.settings) + tuple(self.aliases), + message="Non-existing setting '%s'." % name + ) + + def _lex_error(self, setting, values, error): + setting.set_error(error) + for token in values: + token.type = Token.COMMENT + + def _lex_setting(self, setting, values, name): + self.settings[name] = values + setting.type = name.upper().replace(' ', '_') + if name in self.name_and_arguments: + self._lex_name_and_arguments(values) + elif name in self.name_arguments_and_with_name: + self._lex_name_arguments_and_with_name(values) + else: + self._lex_arguments(values) + + def _lex_name_and_arguments(self, tokens): + if tokens: + tokens[0].type = Token.NAME + self._lex_arguments(tokens[1:]) + + def _lex_name_arguments_and_with_name(self, tokens): + self._lex_name_and_arguments(tokens) + if len(tokens) > 1 and \ + normalize_whitespace(tokens[-2].value) == 'WITH NAME': + tokens[-2].type = Token.WITH_NAME + tokens[-1].type = Token.NAME + + def _lex_arguments(self, tokens): + for token in tokens: + token.type = Token.ARGUMENT + + +class TestCaseFileSettings(Settings): + names = ( + 'Documentation', + 'Metadata', + 'Suite Setup', + 'Suite Teardown', + 'Test Setup', + 'Test Teardown', + 'Test Template', + 'Test Timeout', + 'Force Tags', + 'Default Tags', + 'Library', + 'Resource', + 'Variables' + ) + aliases = { + 'Task Setup': 'Test Setup', + 'Task Teardown': 'Test Teardown', + 'Task Template': 'Test Template', + 'Task Timeout': 'Test Timeout', + } + + +class InitFileSettings(Settings): + names = ( + 'Documentation', + 'Metadata', + 'Suite Setup', + 'Suite Teardown', + 'Test Setup', + 'Test Teardown', + 'Test Timeout', + 'Force Tags', + 'Library', + 'Resource', + 'Variables' + ) + + +class ResourceFileSettings(Settings): + names = ( + 'Documentation', + 'Library', + 'Resource', + 'Variables' + ) + + +class TestCaseSettings(Settings): + names = ( + 'Documentation', + 'Tags', + 'Setup', + 'Teardown', + 'Template', + 'Timeout' + ) + + def __init__(self, parent): + Settings.__init__(self) + self.parent = parent + + def _format_name(self, name): + return name[1:-1].strip() + + @property + def template_set(self): + template = self.settings['Template'] + if self._has_disabling_value(template): + return False + parent_template = self.parent.settings['Test Template'] + return self._has_value(template) or self._has_value(parent_template) + + def _has_disabling_value(self, setting): + if setting is None: + return False + return setting == [] or setting[0].value.upper() == 'NONE' + + def _has_value(self, setting): + return setting and setting[0].value + + +class KeywordSettings(Settings): + names = ( + 'Documentation', + 'Arguments', + 'Teardown', + 'Timeout', + 'Tags', + 'Return' + ) + + def _format_name(self, name): + return name[1:-1].strip() diff --git a/robot/lib/python3.8/site-packages/robot/parsing/lexer/statementlexers.py b/robot/lib/python3.8/site-packages/robot/parsing/lexer/statementlexers.py new file mode 100644 index 0000000000000000000000000000000000000000..53a85c07eafcdb36c90ceaae30f36513d2553186 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/lexer/statementlexers.py @@ -0,0 +1,202 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import normalize_whitespace, split_from_equals +from robot.variables import is_assign, is_dict_variable, search_variable + +from .tokens import Token + + +class Lexer(object): + """Base class for lexers.""" + + def __init__(self, ctx): + self.ctx = ctx + + def handles(self, statement): + return True + + def accepts_more(self, statement): + raise NotImplementedError + + def input(self, statement): + raise NotImplementedError + + def lex(self): + raise NotImplementedError + + +class StatementLexer(Lexer): + token_type = None + + def __init__(self, ctx): + Lexer.__init__(self, ctx) + self.statement = None + + def accepts_more(self, statement): + return False + + def input(self, statement): + self.statement = statement + + def lex(self): + for token in self.statement: + token.type = self.token_type + + +class SectionHeaderLexer(StatementLexer): + + def handles(self, statement): + return statement[0].value.startswith('*') + + +class SettingSectionHeaderLexer(SectionHeaderLexer): + token_type = Token.SETTING_HEADER + + +class VariableSectionHeaderLexer(SectionHeaderLexer): + token_type = Token.VARIABLE_HEADER + + +class TestCaseSectionHeaderLexer(SectionHeaderLexer): + token_type = Token.TESTCASE_HEADER + + +class KeywordSectionHeaderLexer(SectionHeaderLexer): + token_type = Token.KEYWORD_HEADER + + +class CommentSectionHeaderLexer(SectionHeaderLexer): + token_type = Token.COMMENT_HEADER + + +class ErrorSectionHeaderLexer(SectionHeaderLexer): + + def lex(self): + self.ctx.lex_invalid_section(self.statement) + + +class CommentLexer(StatementLexer): + token_type = Token.COMMENT + + +class SettingLexer(StatementLexer): + + def lex(self): + self.ctx.lex_setting(self.statement) + + +class TestOrKeywordSettingLexer(SettingLexer): + + def handles(self, statement): + marker = statement[0].value + return marker and marker[0] == '[' and marker[-1] == ']' + + +class VariableLexer(StatementLexer): + + def lex(self): + name = self.statement[0] + values = self.statement[1:] + match = search_variable(name.value, ignore_errors=True) + if match.is_assign(allow_assign_mark=True): + self._valid_variable(name, values) + else: + self._invalid_variable(name, values) + if match.is_dict_assign(allow_assign_mark=True): + self._validate_dict_items(values) + + def _valid_variable(self, name, values): + name.type = Token.VARIABLE + for token in values: + token.type = Token.ARGUMENT + + def _invalid_variable(self, name, values): + name.set_error("Invalid variable name '%s'." % name.value) + for token in values: + token.type = Token.COMMENT + + def _validate_dict_items(self, values): + for token in values: + if not self._is_valid_dict_item(token.value): + token.set_error( + "Invalid dictionary variable item '%s'. " + "Items must use 'name=value' syntax or be dictionary " + "variables themselves." % token.value + ) + + def _is_valid_dict_item(self, item): + name, value = split_from_equals(item) + return value is not None or is_dict_variable(item) + + +class KeywordCallLexer(StatementLexer): + + def lex(self): + if self.ctx.template_set: + self._lex_as_template() + else: + self._lex_as_keyword_call() + + def _lex_as_template(self): + for token in self.statement: + token.type = Token.ARGUMENT + + def _lex_as_keyword_call(self): + keyword_seen = False + for token in self.statement: + if keyword_seen: + token.type = Token.ARGUMENT + elif is_assign(token.value, allow_assign_mark=True): + token.type = Token.ASSIGN + else: + token.type = Token.KEYWORD + keyword_seen = True + + +class ForLoopHeaderLexer(StatementLexer): + separators = ('IN', 'IN RANGE', 'IN ENUMERATE', 'IN ZIP') + + def handles(self, statement): + marker = statement[0].value + return (marker == 'FOR' or + marker.startswith(':') and + marker.replace(':', '').replace(' ', '').upper() == 'FOR') + + def lex(self): + separator_seen = False + variable_seen = False + self.statement[0].type = Token.FOR + for token in self.statement[1:]: + if separator_seen: + token.type = Token.ARGUMENT + elif variable_seen and self._is_separator(token.value): + token.type = Token.FOR_SEPARATOR + separator_seen = True + else: + token.type = Token.VARIABLE + variable_seen = True + + def _is_separator(self, value): + return normalize_whitespace(value) in self.separators + + +class EndLexer(StatementLexer): + + def handles(self, statement): + return len(statement) == 1 and statement[0].value == 'END' + + def lex(self): + self.statement[0].type = Token.END diff --git a/robot/lib/python3.8/site-packages/robot/parsing/lexer/tokenizer.py b/robot/lib/python3.8/site-packages/robot/parsing/lexer/tokenizer.py new file mode 100644 index 0000000000000000000000000000000000000000..517f138458a4a3e376f755d61184c572fd15550f --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/lexer/tokenizer.py @@ -0,0 +1,137 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +from robot.utils import rstrip + +from .tokens import Token + + +class Tokenizer(object): + _space_splitter = re.compile(r'(\s{2,}|\t)', re.UNICODE) + _pipe_splitter = re.compile(r'((?:\A|\s+)\|(?:\s+|\Z))', re.UNICODE) + + def tokenize(self, data, data_only=False): + current = [] + for lineno, line in enumerate(data.splitlines(not data_only), start=1): + tokens = self._tokenize_line(line, lineno, not data_only) + tokens, starts_new = self._cleanup_tokens(tokens, data_only) + if starts_new: + if current: + yield current + current = tokens + else: + current.extend(tokens) + yield current + + def _tokenize_line(self, line, lineno, include_separators=True): + # Performance optimized code. + tokens = [] + append = tokens.append + offset = 0 + if line[:1] != '|': + splitter = self._split_from_spaces + else: + splitter = self._split_from_pipes + for value, is_data in splitter(rstrip(line)): + if is_data: + append(Token(None, value, lineno, offset)) + elif include_separators: + append(Token(Token.SEPARATOR, value, lineno, offset)) + offset += len(value) + if include_separators: + trailing_whitespace = line[len(rstrip(line)):] + append(Token(Token.EOL, trailing_whitespace, lineno, offset)) + return tokens + + def _split_from_spaces(self, line): + is_data = True + for value in self._space_splitter.split(line): + yield value, is_data + is_data = not is_data + + def _split_from_pipes(self, line): + splitter = self._pipe_splitter + _, separator, rest = splitter.split(line, 1) + yield separator, False + while splitter.search(rest): + token, separator, rest = splitter.split(rest, 1) + yield token, True + yield separator, False + yield rest, True + + def _cleanup_tokens(self, tokens, data_only): + has_data = self._handle_comments(tokens) + continues = self._handle_continuation(tokens) + self._remove_trailing_empty(tokens) + if continues: + self._remove_leading_empty(tokens) + self._ensure_data_after_continuation(tokens) + if data_only: + tokens = self._remove_non_data(tokens) + return tokens, has_data and not continues + + def _handle_comments(self, tokens): + has_data = False + commented = False + for token in tokens: + if token.type is None: + if token.value.startswith('#') or commented: + token.type = Token.COMMENT + commented = True + elif token.value: + has_data = True + return has_data + + def _handle_continuation(self, tokens): + for token in tokens: + if token.value == '...' and token.type is None: + token.type = Token.CONTINUATION + return True + elif token.value and token.type != Token.SEPARATOR: + return False + return False + + def _remove_trailing_empty(self, tokens): + # list() needed w/ IronPython, otherwise reversed() alone is enough. + # https://github.com/IronLanguages/ironpython2/issues/699 + for token in reversed(list(tokens)): + if not token.value and token.type != Token.EOL: + tokens.remove(token) + elif token.type is None: + break + + def _remove_leading_empty(self, tokens): + data_or_continuation = (None, Token.CONTINUATION) + for token in list(tokens): + if not token.value: + tokens.remove(token) + elif token.type in data_or_continuation: + break + + def _ensure_data_after_continuation(self, tokens): + if not any(t.type is None for t in tokens): + cont = self._find_continuation(tokens) + token = Token(lineno=cont.lineno, col_offset=cont.end_col_offset) + tokens.insert(tokens.index(cont) + 1, token) + + def _find_continuation(self, tokens): + for token in tokens: + if token.type == Token.CONTINUATION: + return token + + def _remove_non_data(self, tokens): + return [t for t in tokens if t.type is None] diff --git a/robot/lib/python3.8/site-packages/robot/parsing/lexer/tokens.py b/robot/lib/python3.8/site-packages/robot/parsing/lexer/tokens.py new file mode 100644 index 0000000000000000000000000000000000000000..e40c7f9dc8d1a47b55d35d165343ecf62da030e4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/lexer/tokens.py @@ -0,0 +1,207 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import py2to3 +from robot.variables import VariableIterator + + +@py2to3 +class Token(object): + """Token representing piece of Robot Framework data. + + Each token has type, value, line number, column offset and end column + offset in :attr:`type`, :attr:`value`, :attr:`lineno`, :attr:`col_offset` + and :attr:`end_col_offset` attributes, respectively. Tokens representing + error also have their error message in :attr:`error` attribute. + + Token types are declared as class attributes. + """ + + SETTING_HEADER = 'SETTING_HEADER' + VARIABLE_HEADER = 'VARIABLE_HEADER' + TESTCASE_HEADER = 'TESTCASE_HEADER' + KEYWORD_HEADER = 'KEYWORD_HEADER' + COMMENT_HEADER = 'COMMENT_HEADER' + + TESTCASE_NAME = 'TESTCASE_NAME' + KEYWORD_NAME = 'KEYWORD_NAME' + + DOCUMENTATION = 'DOCUMENTATION' + SUITE_SETUP = 'SUITE_SETUP' + SUITE_TEARDOWN = 'SUITE_TEARDOWN' + METADATA = 'METADATA' + TEST_SETUP = 'TEST_SETUP' + TEST_TEARDOWN = 'TEST_TEARDOWN' + TEST_TEMPLATE = 'TEST_TEMPLATE' + TEST_TIMEOUT = 'TEST_TIMEOUT' + FORCE_TAGS = 'FORCE_TAGS' + DEFAULT_TAGS = 'DEFAULT_TAGS' + LIBRARY = 'LIBRARY' + RESOURCE = 'RESOURCE' + VARIABLES = 'VARIABLES' + SETUP = 'SETUP' + TEARDOWN = 'TEARDOWN' + TEMPLATE = 'TEMPLATE' + TIMEOUT = 'TIMEOUT' + TAGS = 'TAGS' + ARGUMENTS = 'ARGUMENTS' + RETURN = 'RETURN' + + NAME = 'NAME' + VARIABLE = 'VARIABLE' + ARGUMENT = 'ARGUMENT' + ASSIGN = 'ASSIGN' + KEYWORD = 'KEYWORD' + WITH_NAME = 'WITH_NAME' + FOR = 'FOR' + FOR_SEPARATOR = 'FOR_SEPARATOR' + OLD_FOR_INDENT = 'OLD_FOR_INDENT' + END = 'END' + + SEPARATOR = 'SEPARATOR' + COMMENT = 'COMMENT' + CONTINUATION = 'CONTINUATION' + EOL = 'EOL' + EOS = 'EOS' + + ERROR = 'ERROR' + FATAL_ERROR = 'FATAL_ERROR' + + NON_DATA_TOKENS = ( + SEPARATOR, + COMMENT, + CONTINUATION, + EOL, + EOS + ) + SETTING_TOKENS = ( + DOCUMENTATION, + SUITE_SETUP, + SUITE_TEARDOWN, + METADATA, + TEST_SETUP, + TEST_TEARDOWN, + TEST_TEMPLATE, + TEST_TIMEOUT, + FORCE_TAGS, + DEFAULT_TAGS, + LIBRARY, + RESOURCE, + VARIABLES, + SETUP, + TEARDOWN, + TEMPLATE, + TIMEOUT, + TAGS, + ARGUMENTS, + RETURN + ) + HEADER_TOKENS = ( + SETTING_HEADER, + VARIABLE_HEADER, + TESTCASE_HEADER, + KEYWORD_HEADER, + COMMENT_HEADER + ) + ALLOW_VARIABLES = ( + NAME, + ARGUMENT, + TESTCASE_NAME, + KEYWORD_NAME + ) + + __slots__ = ['type', 'value', 'lineno', 'col_offset', 'error'] + + def __init__(self, type=None, value='', lineno=-1, col_offset=-1, error=None): + self.type = type + self.value = value + self.lineno = lineno + self.col_offset = col_offset + self.error = error + + @property + def end_col_offset(self): + if self.col_offset == -1: + return -1 + return self.col_offset + len(self.value) + + def set_error(self, error, fatal=False): + self.type = Token.ERROR if not fatal else Token.FATAL_ERROR + self.error = error + + def tokenize_variables(self): + """Tokenizes possible variables in token value. + + Yields the token itself if the token does not allow variables (see + :attr:`Token.ALLOW_VARIABLES`) or its value does not contain + variables. Otherwise yields variable tokens as well as tokens + before, after, or between variables so that they have the same + type as the original token. + """ + if self.type not in Token.ALLOW_VARIABLES: + return self._tokenize_no_variables() + variables = VariableIterator(self.value) + if not variables: + return self._tokenize_no_variables() + return self._tokenize_variables(variables) + + def _tokenize_no_variables(self): + yield self + + def _tokenize_variables(self, variables): + lineno = self.lineno + col_offset = self.col_offset + remaining = '' + for before, variable, remaining in variables: + if before: + yield Token(self.type, before, lineno, col_offset) + col_offset += len(before) + yield Token(Token.VARIABLE, variable, lineno, col_offset) + col_offset += len(variable) + if remaining: + yield Token(self.type, remaining, lineno, col_offset) + + def __unicode__(self): + return self.value + + def __repr__(self): + error = '' if not self.error else ', %r' % self.error + return 'Token(%s, %r, %s, %s%s)' % (self.type, self.value, + self.lineno, self.col_offset, + error) + + def __eq__(self, other): + if not isinstance(other, Token): + return False + return (self.type == other.type and + self.value == other.value and + self.lineno == other.lineno and + self.col_offset == other.col_offset and + self.error == other.error) + + def __ne__(self, other): + return not self == other + + +class EOS(Token): + """Token representing end of statement.""" + __slots__ = [] + + def __init__(self, lineno=-1, col_offset=-1): + Token.__init__(self, Token.EOS, '', lineno, col_offset) + + @classmethod + def from_token(cls, token): + return EOS(lineno=token.lineno, col_offset=token.end_col_offset) diff --git a/robot/lib/python3.8/site-packages/robot/parsing/model/__init__.py b/robot/lib/python3.8/site-packages/robot/parsing/model/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b019a78d460e05b078ffd377986d1dc3c33813d9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/model/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .blocks import (File, SettingSection, VariableSection, TestCaseSection, + KeywordSection, CommentSection, TestCase, Keyword, ForLoop) +from .statements import Statement +from .visitor import ModelTransformer, ModelVisitor diff --git a/robot/lib/python3.8/site-packages/robot/parsing/model/blocks.py b/robot/lib/python3.8/site-packages/robot/parsing/model/blocks.py new file mode 100644 index 0000000000000000000000000000000000000000..b79dcc7d84bfe1e79fb156f7e923e51afaeb1cd2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/model/blocks.py @@ -0,0 +1,208 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import ast + +from robot.utils import file_writer, is_pathlike, is_string + +from .visitor import ModelVisitor + + +class Block(ast.AST): + _fields = () + _attributes = ('lineno', 'col_offset', 'end_lineno', 'end_col_offset') + + @property + def lineno(self): + statement = FirstStatementFinder.find_from(self) + return statement.lineno if statement else -1 + + @property + def col_offset(self): + statement = FirstStatementFinder.find_from(self) + return statement.col_offset if statement else -1 + + @property + def end_lineno(self): + statement = LastStatementFinder.find_from(self) + return statement.end_lineno if statement else -1 + + @property + def end_col_offset(self): + statement = LastStatementFinder.find_from(self) + return statement.end_col_offset if statement else -1 + + +class File(Block): + _fields = ('sections',) + _attributes = ('source',) + Block._attributes + + def __init__(self, sections=None, source=None): + self.sections = sections or [] + self.source = source + + def save(self, output=None): + """Save model to the given ``output`` or to the original source file. + + The ``output`` can be a path to a file or an already opened file + object. If ``output`` is not given, the original source file will + be overwritten. + """ + output = output or self.source + if output is None: + raise TypeError('Saving model requires explicit output ' + 'when original source is not path.') + ModelWriter(output).write(self) + + +class Section(Block): + _fields = ('header', 'body') + + def __init__(self, header=None, body=None): + self.header = header + self.body = body or [] + + +class SettingSection(Section): + pass + + +class VariableSection(Section): + pass + + +class TestCaseSection(Section): + + @property + def tasks(self): + return self.header.name.upper() in ('TASKS', 'TASK') + + +class KeywordSection(Section): + pass + + +class CommentSection(Section): + pass + + +class TestCase(Block): + _fields = ('header', 'body') + + def __init__(self, header, body=None): + self.header = header + self.body = body or [] + + @property + def name(self): + return self.header.name + + +class Keyword(Block): + _fields = ('header', 'body') + + def __init__(self, header, body=None): + self.header = header + self.body = body or [] + + @property + def name(self): + return self.header.name + + +class ForLoop(Block): + _fields = ('header', 'body', 'end') + + def __init__(self, header, body=None, end=None): + self.header = header + self.body = body or [] + self.end = end + + @property + def variables(self): + return self.header.variables + + @property + def values(self): + return self.header.values + + @property + def flavor(self): + return self.header.flavor + + @property + def _header(self): + return self.header._header + + @property + def _end(self): + return self.end.value if self.end else None + + +class ModelWriter(ModelVisitor): + + def __init__(self, output): + if is_string(output) or is_pathlike(output): + self.writer = file_writer(output) + self.close_writer = True + else: + self.writer = output + self.close_writer = False + + def write(self, model): + try: + self.visit(model) + finally: + if self.close_writer: + self.writer.close() + + def visit_Statement(self, statement): + for token in statement.tokens: + self.writer.write(token.value) + + +class FirstStatementFinder(ModelVisitor): + + def __init__(self): + self.statement = None + + @classmethod + def find_from(cls, model): + finder = cls() + finder.visit(model) + return finder.statement + + def visit_Statement(self, statement): + if self.statement is None: + self.statement = statement + + def generic_visit(self, node): + if self.statement is None: + ModelVisitor.generic_visit(self, node) + + +class LastStatementFinder(ModelVisitor): + + def __init__(self): + self.statement = None + + @classmethod + def find_from(cls, model): + finder = cls() + finder.visit(model) + return finder.statement + + def visit_Statement(self, statement): + self.statement = statement diff --git a/robot/lib/python3.8/site-packages/robot/parsing/model/statements.py b/robot/lib/python3.8/site-packages/robot/parsing/model/statements.py new file mode 100644 index 0000000000000000000000000000000000000000..762ad4e8d5c0f344411f0bdb2c1eb8028623f67f --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/model/statements.py @@ -0,0 +1,462 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import ast +import re + +from robot.utils import normalize_whitespace + +from ..lexer import Token + + +class Statement(ast.AST): + type = None + _fields = ('type', 'tokens') + _attributes = ('lineno', 'col_offset', 'end_lineno', 'end_col_offset') + _statement_handlers = {} + + def __init__(self, tokens): + self.tokens = tuple(tokens) + + @property + def lineno(self): + return self.tokens[0].lineno if self.tokens else -1 + + @property + def col_offset(self): + return self.tokens[0].col_offset if self.tokens else -1 + + @property + def end_lineno(self): + return self.tokens[-1].lineno if self.tokens else -1 + + @property + def end_col_offset(self): + return self.tokens[-1].end_col_offset if self.tokens else -1 + + @classmethod + def register(cls, subcls): + cls._statement_handlers[subcls.type] = subcls + if subcls.type == Token.KEYWORD: + cls._statement_handlers[Token.ASSIGN] = subcls + if subcls.type == Token.ERROR: + cls._statement_handlers[Token.FATAL_ERROR] = subcls + return subcls + + @classmethod + def from_tokens(cls, tokens): + handlers = cls._statement_handlers + for token in tokens: + if token.type in handlers: + return handlers[token.type](tokens) + return EmptyLine(tokens) + + @property + def data_tokens(self): + return [t for t in self.tokens if t.type not in Token.NON_DATA_TOKENS] + + def get_token(self, type): + """Return a token with the given ``type``. + + If there are no matches, return ``None``. If there are multiple + matches, return the first match. + """ + for t in self.tokens: + if t.type == type: + return t + return None + + def get_tokens(self, *types): + """Return tokens having any of the given ``types``.""" + return [t for t in self.tokens if t.type in types] + + def get_value(self, type, default=None): + """Return value of a token with the given ``type``. + + If there are no matches, return ``default``. If there are multiple + matches, return the value of the first match. + """ + token = self.get_token(type) + return token.value if token else default + + def get_values(self, *types): + """Return values of tokens having any of the given ``types``.""" + return tuple(t.value for t in self.tokens if t.type in types) + + @property + def lines(self): + line = [] + for token in self.tokens: + line.append(token) + if token.type == Token.EOL: + yield line + line = [] + if line: + yield line + + @property + def error(self): + tokens = self.get_tokens(Token.ERROR, Token.FATAL_ERROR) + if not tokens: + return None + if len(tokens) == 1: + return tokens[0].error + errors = ['%d) %s' % (i+1, t.error) for i, t in enumerate(tokens)] + return '\n\n'.join(['Multiple errors:'] + errors) + + def __len__(self): + return len(self.tokens) + + def __getitem__(self, item): + return self.tokens[item] + + +class DocumentationOrMetadata(Statement): + + def _join_value(self, tokens): + lines = self._get_lines(tokens) + return ''.join(self._yield_lines_with_newlines(lines)) + + def _get_lines(self, tokens): + lines = [] + line = None + lineno = -1 + for t in tokens: + if t.lineno != lineno: + line = [] + lines.append(line) + line.append(t.value) + lineno = t.lineno + return [' '.join(line) for line in lines] + + def _yield_lines_with_newlines(self, lines): + last_index = len(lines) - 1 + for index, line in enumerate(lines): + yield line + if index < last_index and not self._escaped_or_has_newline(line): + yield '\n' + + def _escaped_or_has_newline(self, line): + match = re.search(r'(\\+)n?$', line) + return match and len(match.group(1)) % 2 == 1 + + +class SingleValue(Statement): + + @property + def value(self): + values = self.get_values(Token.NAME, Token.ARGUMENT) + if values and values[0].upper() != 'NONE': + return values[0] + return None + + +class MultiValue(Statement): + + @property + def values(self): + return self.get_values(Token.ARGUMENT) + + +class Fixture(Statement): + + @property + def name(self): + return self.get_value(Token.NAME) + + @property + def args(self): + return self.get_values(Token.ARGUMENT) + + +class SectionHeader(Statement): + + @property + def name(self): + header = self.get_token(self.type) + return normalize_whitespace(header.value).strip('* ') + + +@Statement.register +class SettingSectionHeader(SectionHeader): + type = Token.SETTING_HEADER + + +@Statement.register +class VariableSectionHeader(SectionHeader): + type = Token.VARIABLE_HEADER + + +@Statement.register +class TestCaseSectionHeader(SectionHeader): + type = Token.TESTCASE_HEADER + + +@Statement.register +class KeywordSectionHeader(SectionHeader): + type = Token.KEYWORD_HEADER + + +@Statement.register +class CommentSectionHeader(SectionHeader): + type = Token.COMMENT_HEADER + + +@Statement.register +class LibraryImport(Statement): + type = Token.LIBRARY + + @property + def name(self): + return self.get_value(Token.NAME) + + @property + def args(self): + return self.get_values(Token.ARGUMENT) + + @property + def alias(self): + with_name = self.get_token(Token.WITH_NAME) + return self.get_tokens(Token.NAME)[-1].value if with_name else None + + +@Statement.register +class ResourceImport(Statement): + type = Token.RESOURCE + + @property + def name(self): + return self.get_value(Token.NAME) + + +@Statement.register +class VariablesImport(Statement): + type = Token.VARIABLES + + @property + def name(self): + return self.get_value(Token.NAME) + + @property + def args(self): + return self.get_values(Token.ARGUMENT) + + +@Statement.register +class Documentation(DocumentationOrMetadata): + type = Token.DOCUMENTATION + + @property + def value(self): + tokens = self.get_tokens(Token.ARGUMENT) + return self._join_value(tokens) + + +@Statement.register +class Metadata(DocumentationOrMetadata): + type = Token.METADATA + + @property + def name(self): + return self.get_value(Token.NAME) + + @property + def value(self): + tokens = self.get_tokens(Token.ARGUMENT) + return self._join_value(tokens) + + +@Statement.register +class ForceTags(MultiValue): + type = Token.FORCE_TAGS + + +@Statement.register +class DefaultTags(MultiValue): + type = Token.DEFAULT_TAGS + + +@Statement.register +class SuiteSetup(Fixture): + type = Token.SUITE_SETUP + + +@Statement.register +class SuiteTeardown(Fixture): + type = Token.SUITE_TEARDOWN + + +@Statement.register +class TestSetup(Fixture): + type = Token.TEST_SETUP + + +@Statement.register +class TestTeardown(Fixture): + type = Token.TEST_TEARDOWN + + +@Statement.register +class TestTemplate(SingleValue): + type = Token.TEST_TEMPLATE + + +@Statement.register +class TestTimeout(SingleValue): + type = Token.TEST_TIMEOUT + + +@Statement.register +class Variable(Statement): + type = Token.VARIABLE + + @property + def name(self): + name = self.get_value(Token.VARIABLE) + if name.endswith('='): + return name[:-1].rstrip() + return name + + @property + def value(self): + return self.get_values(Token.ARGUMENT) + + +@Statement.register +class TestCaseName(Statement): + type = Token.TESTCASE_NAME + + @property + def name(self): + return self.get_value(Token.TESTCASE_NAME) + + +@Statement.register +class KeywordName(Statement): + type = Token.KEYWORD_NAME + + @property + def name(self): + return self.get_value(Token.KEYWORD_NAME) + + +@Statement.register +class Setup(Fixture): + type = Token.SETUP + + +@Statement.register +class Teardown(Fixture): + type = Token.TEARDOWN + + +@Statement.register +class Tags(MultiValue): + type = Token.TAGS + + +@Statement.register +class Template(SingleValue): + type = Token.TEMPLATE + + +@Statement.register +class Timeout(SingleValue): + type = Token.TIMEOUT + + +@Statement.register +class Arguments(MultiValue): + type = Token.ARGUMENTS + + +@Statement.register +class Return(MultiValue): + type = Token.RETURN + + +@Statement.register +class KeywordCall(Statement): + type = Token.KEYWORD + + @property + def keyword(self): + return self.get_value(Token.KEYWORD) + + @property + def args(self): + return self.get_values(Token.ARGUMENT) + + @property + def assign(self): + return self.get_values(Token.ASSIGN) + + +@Statement.register +class TemplateArguments(Statement): + type = Token.ARGUMENT + + @property + def args(self): + return self.get_values(self.type) + + +@Statement.register +class ForLoopHeader(Statement): + type = Token.FOR + + @property + def variables(self): + return self.get_values(Token.VARIABLE) + + @property + def values(self): + return self.get_values(Token.ARGUMENT) + + @property + def flavor(self): + separator = self.get_token(Token.FOR_SEPARATOR) + return normalize_whitespace(separator.value) if separator else None + + @property + def _header(self): + return self.get_value(Token.FOR) + + +@Statement.register +class End(Statement): + type = Token.END + + @property + def value(self): + return self.get_value(Token.END) + + +@Statement.register +class Comment(Statement): + type = Token.COMMENT + + +@Statement.register +class Error(Statement): + type = Token.ERROR + + +class EmptyLine(Statement): + type = Token.EOL + + @classmethod + def from_value(cls, value): + return EmptyLine([Token(Token.EOL, value)]) diff --git a/robot/lib/python3.8/site-packages/robot/parsing/model/visitor.py b/robot/lib/python3.8/site-packages/robot/parsing/model/visitor.py new file mode 100644 index 0000000000000000000000000000000000000000..b5dd7b29457be8c0a15c2392f54265014945c2a1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/model/visitor.py @@ -0,0 +1,65 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import ast + + +class VisitorFinder(object): + + def _find_visitor(self, cls): + if cls is ast.AST: + return None + method = 'visit_' + cls.__name__ + if hasattr(self, method): + return getattr(self, method) + for base in cls.__bases__: + visitor = self._find_visitor(base) + if visitor: + return visitor + return None + + +class ModelVisitor(ast.NodeVisitor, VisitorFinder): + """NodeVisitor that supports matching nodes based on their base classes. + + Otherwise identical to the standard `ast.NodeVisitor + `__, + but allows creating ``visit_ClassName`` methods so that the ``ClassName`` + is one of the base classes of the node. For example, this visitor method + matches all section headers:: + + def visit_SectionHeader(self, node): + # ... + + If all visitor methods match node classes directly, it is better to use + the standard ``ast.NodeVisitor`` instead. + """ + + def visit(self, node): + visitor = self._find_visitor(type(node)) or self.generic_visit + visitor(node) + + +class ModelTransformer(ast.NodeTransformer, VisitorFinder): + """NodeTransformer that supports matching nodes based on their base classes. + + See :class:`ModelVisitor` for explanation how this is different compared + to the standard `ast.NodeTransformer + `__. + """ + + def visit(self, node): + visitor = self._find_visitor(type(node)) or self.generic_visit + return visitor(node) diff --git a/robot/lib/python3.8/site-packages/robot/parsing/parser/__init__.py b/robot/lib/python3.8/site-packages/robot/parsing/parser/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b6be536be1d1ef3a75445552ae6444859a1bab2c --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/parser/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .parser import get_model, get_resource_model, get_init_model diff --git a/robot/lib/python3.8/site-packages/robot/parsing/parser/blockparsers.py b/robot/lib/python3.8/site-packages/robot/parsing/parser/blockparsers.py new file mode 100644 index 0000000000000000000000000000000000000000..0c67d42684206e37cfd82f4f11d60f398fbd88c1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/parser/blockparsers.py @@ -0,0 +1,92 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ..lexer import Token +from ..model import TestCase, Keyword, ForLoop + + +class Parser(object): + """Base class for parsers.""" + + def __init__(self, model): + self.model = model + + def handles(self, statement): + raise NotImplementedError + + def parse(self, statement): + raise NotImplementedError + + +class TestCaseParser(Parser): + + def __init__(self, header): + Parser.__init__(self, TestCase(header)) + + def handles(self, statement): + if statement.type == Token.TESTCASE_NAME: + return False + return statement.type not in Token.HEADER_TOKENS + + def parse(self, statement): + if statement.type == Token.FOR: + parser = ForLoopParser(statement) + model = parser.model + else: + parser = None + model = statement + self.model.body.append(model) + return parser + + +class KeywordParser(Parser): + + def __init__(self, header): + Parser.__init__(self, Keyword(header)) + + def handles(self, statement): + if statement.type == Token.KEYWORD_NAME: + return False + return statement.type not in Token.HEADER_TOKENS + + def parse(self, statement): + if statement.type == Token.FOR: + parser = ForLoopParser(statement) + model = parser.model + else: + parser = None + model = statement + self.model.body.append(model) + return parser + + +class ForLoopParser(Parser): + + def __init__(self, header): + Parser.__init__(self, ForLoop(header)) + self.end_seen = False + + def handles(self, statement): + if self.end_seen: + return False + name_tokens = (Token.TESTCASE_NAME, Token.KEYWORD_NAME) + return statement.type not in Token.HEADER_TOKENS + name_tokens + + def parse(self, statement): + if statement.type == Token.END: + self.model.end = statement + self.end_seen = True + else: + self.model.body.append(statement) diff --git a/robot/lib/python3.8/site-packages/robot/parsing/parser/fileparser.py b/robot/lib/python3.8/site-packages/robot/parsing/parser/fileparser.py new file mode 100644 index 0000000000000000000000000000000000000000..c2389d00424e326016065729b0d8915bedc60830 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/parser/fileparser.py @@ -0,0 +1,122 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os.path + +from robot.utils import is_pathlike, is_string + +from ..lexer import Token +from ..model import (File, CommentSection, SettingSection, VariableSection, + TestCaseSection, KeywordSection) + +from .blockparsers import Parser, TestCaseParser, KeywordParser + + +class FileParser(Parser): + + def __init__(self, source=None): + Parser.__init__(self, File(source=self._get_path(source))) + + def _get_path(self, source): + if not source: + return None + if is_string(source) and '\n' not in source and os.path.isfile(source): + return source + if is_pathlike(source) and source.is_file(): + return str(source) + return None + + def handles(self, statement): + return True + + def parse(self, statement): + parser_class = { + Token.SETTING_HEADER: SettingSectionParser, + Token.VARIABLE_HEADER: VariableSectionParser, + Token.TESTCASE_HEADER: TestCaseSectionParser, + Token.KEYWORD_HEADER: KeywordSectionParser, + Token.COMMENT_HEADER: CommentSectionParser, + Token.COMMENT: ImplicitCommentSectionParser, + Token.ERROR: ImplicitCommentSectionParser, + Token.EOL: ImplicitCommentSectionParser + }[statement.type] + parser = parser_class(statement) + self.model.sections.append(parser.model) + return parser + + +class SectionParser(Parser): + + def handles(self, statement): + return statement.type not in Token.HEADER_TOKENS + + def parse(self, statement): + self.model.body.append(statement) + + +class SettingSectionParser(SectionParser): + + def __init__(self, header): + SectionParser.__init__(self, SettingSection(header)) + + +class VariableSectionParser(SectionParser): + + def __init__(self, header): + SectionParser.__init__(self, VariableSection(header)) + + +class CommentSectionParser(SectionParser): + + def __init__(self, header): + SectionParser.__init__(self, CommentSection(header)) + + +class ImplicitCommentSectionParser(SectionParser): + + def __init__(self, statement): + SectionParser.__init__(self, CommentSection(body=[statement])) + + +class TestCaseSectionParser(SectionParser): + + def __init__(self, header): + SectionParser.__init__(self, TestCaseSection(header)) + + def parse(self, statement): + if statement.type == Token.TESTCASE_NAME: + parser = TestCaseParser(statement) + model = parser.model + else: # Empty lines and comments before first test. + parser = None + model = statement + self.model.body.append(model) + return parser + + +class KeywordSectionParser(SectionParser): + + def __init__(self, header): + SectionParser.__init__(self, KeywordSection(header)) + + def parse(self, statement): + if statement.type == Token.KEYWORD_NAME: + parser = KeywordParser(statement) + model = parser.model + else: # Empty lines and comments before first keyword. + parser = None + model = statement + self.model.body.append(model) + return parser diff --git a/robot/lib/python3.8/site-packages/robot/parsing/parser/parser.py b/robot/lib/python3.8/site-packages/robot/parsing/parser/parser.py new file mode 100644 index 0000000000000000000000000000000000000000..080e98a16dc0d46d6f8edf3ea9e6582897ba6df0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/parser/parser.py @@ -0,0 +1,95 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ..lexer import Token, get_tokens, get_resource_tokens, get_init_tokens +from ..model import Statement + +from .fileparser import FileParser + + +def get_model(source, data_only=False, curdir=None): + """Parses the given source to a model represented as an AST. + + How to use the model is explained more thoroughly in the general + documentation of the :mod:`robot.parsing` module. + + :param source: The source where to read the data. Can be a path to + a source file as a string or as ``pathlib.Path`` object, an already + opened file object, or Unicode text containing the date directly. + Source files must be UTF-8 encoded. + :param data_only: When ``False`` (default), returns all tokens. When set + to ``True``, omits separators, comments, continuation markers, and + other non-data tokens. Model like this cannot be saved back to + file system. + :param curdir: Directory where the source file exists. This path is used + to set the value of the built-in ``${CURDIR}`` variable during parsing. + When not given, the variable is left as-is. Should only be given + only if the model will be executed afterwards. If the model is saved + back to disk, resolving ``${CURDIR}`` is typically not a good idea. + + Use :func:`get_resource_model` or :func:`get_init_model` when parsing + resource or suite initialization files, respectively. + """ + tokens = get_tokens(source, data_only) + statements = _tokens_to_statements(tokens, curdir) + return _statements_to_model(statements, source) + + +def get_resource_model(source, data_only=False, curdir=None): + """Parses the given source to a resource file model. + + Otherwise same as :func:`get_model` but the source is considered to be + a resource file. This affects, for example, what settings are valid. + """ + tokens = get_resource_tokens(source, data_only) + statements = _tokens_to_statements(tokens, curdir) + return _statements_to_model(statements, source) + + +def get_init_model(source, data_only=False, curdir=None): + """Parses the given source to a init file model. + + Otherwise same as :func:`get_model` but the source is considered to be + a suite initialization file. This affects, for example, what settings are + valid. + """ + tokens = get_init_tokens(source, data_only) + statements = _tokens_to_statements(tokens, curdir) + return _statements_to_model(statements, source) + + +def _tokens_to_statements(tokens, curdir=None): + statement = [] + EOS = Token.EOS + for t in tokens: + if curdir and '${CURDIR}' in t.value: + t.value = t.value.replace('${CURDIR}', curdir) + if t.type != EOS: + statement.append(t) + else: + yield Statement.from_tokens(statement) + statement = [] + + +def _statements_to_model(statements, source=None): + parser = FileParser(source=source) + stack = [parser] + for statement in statements: + while not stack[-1].handles(statement): + stack.pop() + parser = stack[-1].parse(statement) + if parser: + stack.append(parser) + return stack[0].model diff --git a/robot/lib/python3.8/site-packages/robot/parsing/suitestructure.py b/robot/lib/python3.8/site-packages/robot/parsing/suitestructure.py new file mode 100644 index 0000000000000000000000000000000000000000..3c370760b4de48b2545f7c1dd3edb8b3fb66523f --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/parsing/suitestructure.py @@ -0,0 +1,172 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os.path + +from robot.errors import DataError +from robot.model import SuiteNamePatterns +from robot.output import LOGGER +from robot.utils import abspath, get_error_message, unic + + +class SuiteStructure(object): + + def __init__(self, source=None, init_file=None, children=None): + self.source = source + self.init_file = init_file + self.children = children + self.extension = self._get_extension(source, init_file) + + def _get_extension(self, source, init_file): + if self.is_directory and not init_file: + return None + source = init_file or source + return os.path.splitext(source)[1][1:].lower() + + @property + def is_directory(self): + return self.children is not None + + def visit(self, visitor): + if self.children is None: + visitor.visit_file(self) + else: + visitor.visit_directory(self) + + +class SuiteStructureBuilder(object): + ignored_prefixes = ('_', '.') + ignored_dirs = ('CVS',) + + def __init__(self, included_extensions=('robot',), included_suites=None): + self.included_extensions = included_extensions + self.included_suites = included_suites + + def build(self, paths): + paths = list(self._normalize_paths(paths)) + if len(paths) == 1: + return self._build(paths[0], self.included_suites) + children = [self._build(p, self.included_suites) for p in paths] + return SuiteStructure(children=children) + + def _normalize_paths(self, paths): + if not paths: + raise DataError('One or more source paths required.') + for path in paths: + path = os.path.normpath(path) + if not os.path.exists(path): + raise DataError("Parsing '%s' failed: File or directory to " + "execute does not exist." % path) + yield abspath(path) + + def _build(self, path, include_suites): + if os.path.isfile(path): + return SuiteStructure(path) + include_suites = self._get_include_suites(path, include_suites) + init_file, paths = self._get_child_paths(path, include_suites) + children = [self._build(p, include_suites) for p in paths] + return SuiteStructure(path, init_file, children) + + def _get_include_suites(self, path, incl_suites): + if not incl_suites: + return None + if not isinstance(incl_suites, SuiteNamePatterns): + incl_suites = SuiteNamePatterns( + self._create_included_suites(incl_suites)) + # If a directory is included, also all its children should be included. + if self._is_in_included_suites(os.path.basename(path), incl_suites): + return None + return incl_suites + + def _create_included_suites(self, incl_suites): + for suite in incl_suites: + yield suite + while '.' in suite: + suite = suite.split('.', 1)[1] + yield suite + + def _get_child_paths(self, dirpath, incl_suites=None): + init_file = None + paths = [] + for path, is_init_file in self._list_dir(dirpath, incl_suites): + if is_init_file: + if not init_file: + init_file = path + else: + LOGGER.error("Ignoring second test suite init file '%s'." + % path) + else: + paths.append(path) + return init_file, paths + + def _list_dir(self, dir_path, incl_suites): + # os.listdir returns Unicode entries when path is Unicode + dir_path = unic(dir_path) + try: + names = os.listdir(dir_path) + except: + raise DataError("Reading directory '%s' failed: %s" + % (dir_path, get_error_message())) + for name in sorted(names, key=lambda item: item.lower()): + name = unic(name) # needed to handle nfc/nfd normalization on OSX + path = os.path.join(dir_path, name) + base, ext = os.path.splitext(name) + ext = ext[1:].lower() + if self._is_init_file(path, base, ext): + yield path, True + elif self._is_included(path, base, ext, incl_suites): + yield path, False + else: + LOGGER.info("Ignoring file or directory '%s'." % path) + + def _is_init_file(self, path, base, ext): + return (base.lower() == '__init__' + and ext in self.included_extensions + and os.path.isfile(path)) + + def _is_included(self, path, base, ext, incl_suites): + if base.startswith(self.ignored_prefixes): + return False + if os.path.isdir(path): + return base not in self.ignored_dirs or ext + if ext not in self.included_extensions: + return False + return self._is_in_included_suites(base, incl_suites) + + def _is_in_included_suites(self, name, incl_suites): + if not incl_suites: + return True + return incl_suites.match(self._split_prefix(name)) + + def _split_prefix(self, name): + return name.split('__', 1)[-1] + + +class SuiteStructureVisitor(object): + + def visit_file(self, structure): + pass + + def visit_directory(self, structure): + self.start_directory(structure) + for child in structure.children: + child.visit(self) + self.end_directory(structure) + + def start_directory(self, structure): + pass + + def end_directory(self, structure): + pass diff --git a/robot/lib/python3.8/site-packages/robot/pythonpathsetter.py b/robot/lib/python3.8/site-packages/robot/pythonpathsetter.py new file mode 100644 index 0000000000000000000000000000000000000000..930fc7cb7837cf3cd38c958cfa03cc82a91abbe9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/pythonpathsetter.py @@ -0,0 +1,41 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module that adds directories needed by Robot to sys.path when imported.""" + +import sys +import fnmatch +from os.path import abspath, dirname + +ROBOTDIR = dirname(abspath(__file__)) + +def add_path(path, end=False): + if not end: + remove_path(path) + sys.path.insert(0, path) + elif not any(fnmatch.fnmatch(p, path) for p in sys.path): + sys.path.append(path) + +def remove_path(path): + sys.path = [p for p in sys.path if not fnmatch.fnmatch(p, path)] + + +# When, for example, robot/run.py is executed as a script, the directory +# containing the robot module is not added to sys.path automatically but +# the robot directory itself is. Former is added to allow importing +# the module and the latter removed to prevent accidentally importing +# internal modules directly. +add_path(dirname(ROBOTDIR)) +remove_path(ROBOTDIR) diff --git a/robot/lib/python3.8/site-packages/robot/rebot.py b/robot/lib/python3.8/site-packages/robot/rebot.py new file mode 100644 index 0000000000000000000000000000000000000000..f4ea482c3ccfa27e3b489b46b5d4206b62f92132 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/rebot.py @@ -0,0 +1,408 @@ +#!/usr/bin/env python + +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module implementing the command line entry point for post-processing outputs. + +This module can be executed from the command line using the following +approaches:: + + python -m robot.rebot + python path/to/robot/rebot.py + +Instead of ``python`` it is possible to use also other Python interpreters. +This module is also used by the installed ``rebot`` start-up script. + +This module also provides :func:`rebot` and :func:`rebot_cli` functions +that can be used programmatically. Other code is for internal usage. +""" + +import sys + +# Allows running as a script. __name__ check needed with multiprocessing: +# https://github.com/robotframework/robotframework/issues/1137 +if 'robot' not in sys.modules and __name__ == '__main__': + import pythonpathsetter + +from robot.conf import RebotSettings +from robot.errors import DataError +from robot.reporting import ResultWriter +from robot.output import LOGGER +from robot.utils import Application +from robot.run import RobotFramework + + +USAGE = """Rebot -- Robot Framework report and log generator + +Version: + +Usage: rebot [options] robot_outputs + or: python -m robot.rebot [options] robot_outputs + or: python path/to/robot/rebot.py [options] robot_outputs + or: java -jar robotframework.jar rebot [options] robot_outputs + +Rebot can be used to generate logs and reports in HTML format. It can also +produce new XML output files which can be further processed with Rebot or +other tools. + +The easiest way to execute Rebot is using the `rebot` command created as part +of the normal installation. Alternatively it is possible to execute the +`robot.rebot` module directly using `python -m robot.rebot`, where `python` +can be replaced with any supported Python interpreter like `jython`, `ipy` or +`python3`. Yet another alternative is running the `robot/rebot.py` script like +`python path/to/robot/rebot.py`. Finally, there is a standalone JAR +distribution available. + +Inputs to Rebot are XML output files generated by Robot Framework or by earlier +Rebot executions. When more than one input file is given, a new top level test +suite containing suites in the given files is created by default. This allows +combining multiple outputs together to create higher level reports. An +exception is that if --merge is used, results are combined by adding suites +and tests in subsequent outputs into the first suite structure. If same test +is found from multiple outputs, the last one replaces the earlier ones. + +For more information about Rebot and other built-in tools, see +http://robotframework.org/robotframework/#built-in-tools. For more details +about Robot Framework in general, go to http://robotframework.org. + +Options +======= + + --rpa Turn on the generic automation mode. Mainly affects + terminology so that "test" is replaced with "task" + in logs and reports. By default the mode is got + from the processed output files. New in RF 3.1. + -R --merge When combining results, merge outputs together + instead of putting them under a new top level suite. + Example: rebot --merge orig.xml rerun.xml + -N --name name Set the name of the top level suite. + -D --doc documentation Set the documentation of the top level suite. + Simple formatting is supported (e.g. *bold*). If + the documentation contains spaces, it must be quoted. + Example: --doc "Very *good* example" + -M --metadata name:value * Set metadata of the top level suite. Value can + contain formatting similarly as --doc. + Example: --metadata Version:1.2 + -G --settag tag * Sets given tag(s) to all tests. + -t --test name * Select tests by name or by long name containing also + parent suite name like `Parent.Test`. Name is case + and space insensitive and it can also be a simple + pattern where `*` matches anything, `?` matches any + single character, and `[chars]` matches one character + in brackets. + --task name * Alias to --test. Especially applicable with --rpa. + -s --suite name * Select suites by name. When this option is used with + --test, --include or --exclude, only tests in + matching suites and also matching other filtering + criteria are selected. Name can be a simple pattern + similarly as with --test and it can contain parent + name separated with a dot. For example, `-s X.Y` + selects suite `Y` only if its parent is `X`. + -i --include tag * Select tests by tag. Similarly as name with --test, + tag is case and space insensitive and it is possible + to use patterns with `*`, `?` and `[]` as wildcards. + Tags and patterns can also be combined together with + `AND`, `OR`, and `NOT` operators. + Examples: --include foo --include bar* + --include fooANDbar* + -e --exclude tag * Specify tests not to be included by tag. They are not + selected even if included with --include. Tags are + matched using same rules as with --include. + --processemptysuite Processes output also if the top level suite is + empty. Useful e.g. with --include/--exclude when it + is not an error that there are no matches. + -c --critical tag * Tests having the given tag are considered critical. + If no critical tags are set, all tests are critical. + Tags can be given as a pattern same way as with + --include. + -n --noncritical tag * Tests having the given tag are not critical even if + they have a tag set with --critical. Tag can be + a pattern. + -d --outputdir dir Where to create output files. The default is the + directory where Rebot is run from and the given path + is considered relative to that unless it is absolute. + -o --output file XML output file. Not created unless this option is + specified. Given path, similarly as paths given to + --log, --report and --xunit, is relative to + --outputdir unless given as an absolute path. + -l --log file HTML log file. Can be disabled by giving a special + name `NONE`. Default: log.html + Examples: `--log mylog.html`, `-l none` + -r --report file HTML report file. Can be disabled with `NONE` + similarly as --log. Default: report.html + -x --xunit file xUnit compatible result file. Not created unless this + option is specified. + --xunitskipnoncritical Mark non-critical tests in xUnit output as skipped. + -T --timestampoutputs When this option is used, timestamp in a format + `YYYYMMDD-hhmmss` is added to all generated output + files between their basename and extension. For + example `-T -o output.xml -r report.html -l none` + creates files like `output-20070503-154410.xml` and + `report-20070503-154410.html`. + --splitlog Split the log file into smaller pieces that open in + browsers transparently. + --logtitle title Title for the generated log file. The default title + is ` Test Log`. + --reporttitle title Title for the generated report file. The default + title is ` Test Report`. + --reportbackground colors Background colors to use in the report file. + Either `all_passed:critical_passed:failed` or + `passed:failed`. Both color names and codes work. + Examples: --reportbackground green:yellow:red + --reportbackground #00E:#E00 + -L --loglevel level Threshold for selecting messages. Available levels: + TRACE (default), DEBUG, INFO, WARN, NONE (no msgs). + Use syntax `LOGLEVEL:DEFAULT` to define the default + visible log level in log files. + Examples: --loglevel DEBUG + --loglevel DEBUG:INFO + --suitestatlevel level How many levels to show in `Statistics by Suite` + in log and report. By default all suite levels are + shown. Example: --suitestatlevel 3 + --tagstatinclude tag * Include only matching tags in `Statistics by Tag` + in log and report. By default all tags are shown. + Given tag can be a pattern like with --include. + --tagstatexclude tag * Exclude matching tags from `Statistics by Tag`. + This option can be used with --tagstatinclude + similarly as --exclude is used with --include. + --tagstatcombine tags:name * Create combined statistics based on tags. + These statistics are added into `Statistics by Tag`. + If the optional `name` is not given, name of the + combined tag is got from the specified tags. Tags are + matched using the same rules as with --include. + Examples: --tagstatcombine requirement-* + --tagstatcombine tag1ANDtag2:My_name + --tagdoc pattern:doc * Add documentation to tags matching the given + pattern. Documentation is shown in `Test Details` and + also as a tooltip in `Statistics by Tag`. Pattern can + use `*`, `?` and `[]` as wildcards like --test. + Documentation can contain formatting like --doc. + Examples: --tagdoc mytag:Example + --tagdoc "owner-*:Original author" + --tagstatlink pattern:link:title * Add external links into `Statistics by + Tag`. Pattern can use `*`, `?` and `[]` as wildcards + like --test. Characters matching to `*` and `?` + wildcards can be used in link and title with syntax + %N, where N is index of the match (starting from 1). + Examples: --tagstatlink mytag:http://my.domain:Title + --tagstatlink "bug-*:http://url/id=%1:Issue Tracker" + --expandkeywords name:|tag: * + Matching keywords will be automatically expanded in + the log file. Matching against keyword name or tags + work using same rules as with --removekeywords. + Examples: --expandkeywords name:BuiltIn.Log + --expandkeywords tag:expand + New in RF 3.2. + --removekeywords all|passed|for|wuks|name:|tag: * + Remove keyword data from all generated outputs. + Keywords containing warnings are not removed except + in the `all` mode. + all: remove data from all keywords + passed: remove data only from keywords in passed + test cases and suites + for: remove passed iterations from for loops + wuks: remove all but the last failing keyword + inside `BuiltIn.Wait Until Keyword Succeeds` + name:: remove data from keywords that match + the given pattern. The pattern is matched + against the full name of the keyword (e.g. + 'MyLib.Keyword', 'resource.Second Keyword'), + is case, space, and underscore insensitive, + and may contain `*`, `?` and `[]` wildcards. + Examples: --removekeywords name:Lib.HugeKw + --removekeywords name:myresource.* + tag:: remove data from keywords that match + the given pattern. Tags are case and space + insensitive and patterns can contain `*`, + `?` and `[]` wildcards. Tags and patterns + can also be combined together with `AND`, + `OR`, and `NOT` operators. + Examples: --removekeywords foo + --removekeywords fooANDbar* + --flattenkeywords for|foritem|name:|tag: * + Flattens matching keywords in all generated outputs. + Matching keywords get all log messages from their + child keywords and children are discarded otherwise. + for: flatten for loops fully + foritem: flatten individual for loop iterations + name:: flatten matched keywords using same + matching rules as with + `--removekeywords name:` + tag:: flatten matched keywords using same + matching rules as with + `--removekeywords tag:` + --starttime timestamp Set execution start time. Timestamp must be given in + format `2007-10-01 15:12:42.268` where all separators + are optional (e.g. `20071001151242268` is ok too) and + parts from milliseconds to hours can be omitted if + they are zero (e.g. `2007-10-01`). This can be used + to override start time of a single suite or to set + start time for a combined suite, which would + otherwise be `N/A`. + --endtime timestamp Same as --starttime but for end time. If both options + are used, elapsed time of the suite is calculated + based on them. For combined suites, it is otherwise + calculated by adding elapsed times of the combined + suites together. + --nostatusrc Sets the return code to zero regardless are there + failures. Error codes are returned normally. + --prerebotmodifier class * Class to programmatically modify the result + model before creating outputs. + -C --consolecolors auto|on|ansi|off Use colors on console output or not. + auto: use colors when output not redirected (default) + on: always use colors + ansi: like `on` but use ANSI colors also on Windows + off: disable colors altogether + Note that colors do not work with Jython on Windows. + -P --pythonpath path * Additional locations to add to the module search path + that is used when importing Python based extensions. + -A --argumentfile path * Text file to read more arguments from. File can have + both options and output files, one per line. Contents + do not need to be escaped but spaces in the beginning + and end of lines are removed. Empty lines and lines + starting with a hash character (#) are ignored. + Example file: + | --include regression + | --name Regression Tests + | # This is a comment line + | output.xml + -h -? --help Print usage instructions. + --version Print version information. + +Options that are marked with an asterisk (*) can be specified multiple times. +For example, `--test first --test third` selects test cases with name `first` +and `third`. If an option accepts a value but is not marked with an asterisk, +the last given value has precedence. For example, `--log A.html --log B.html` +creates log file `B.html`. Options accepting no values can be disabled by +using the same option again with `no` prefix added or dropped. The last option +has precedence regardless of how many times options are used. For example, +`--merge --merge --nomerge --nostatusrc --statusrc` would not activate the +merge mode and would return a normal return code. + +Long option format is case-insensitive. For example, --SuiteStatLevel is +equivalent to but easier to read than --suitestatlevel. Long options can +also be shortened as long as they are unique. For example, `--logti Title` +works while `--lo log.html` does not because the former matches only --logtitle +but the latter matches both --log and --logtitle. + +Environment Variables +===================== + +REBOT_OPTIONS Space separated list of default options to be placed + in front of any explicit options on the command line. +ROBOT_SYSLOG_FILE Path to a file where Robot Framework writes internal + information about processed files. Can be useful when + debugging problems. If not set, or set to special + value `NONE`, writing to the syslog file is disabled. +ROBOT_SYSLOG_LEVEL Log level to use when writing to the syslog file. + Available levels are the same as for --loglevel + command line option and the default is INFO. + +Examples +======== + +# Simple Rebot run that creates log and report with default names. +$ rebot output.xml + +# Using options. Note that this is one long command split into multiple lines. +$ rebot --log smoke_log.html --report smoke_report.html --include smoke + --ReportTitle "Smoke Tests" --ReportBackground green:yellow:red + --TagStatCombine tag1ANDtag2 path/to/myoutput.xml + +# Executing `robot.rebot` module using Python and creating combined outputs. +$ python -m robot.rebot --name Combined outputs/*.xml + +# Running `robot/rebot.py` script with Jython. +$ jython path/robot/rebot.py -N Project_X -l none -r x.html output.xml +""" + + +class Rebot(RobotFramework): + + def __init__(self): + Application.__init__(self, USAGE, arg_limits=(1,), + env_options='REBOT_OPTIONS', logger=LOGGER) + + def main(self, datasources, **options): + settings = RebotSettings(options) + LOGGER.register_console_logger(**settings.console_output_config) + LOGGER.disable_message_cache() + rc = ResultWriter(*datasources).write_results(settings) + if rc < 0: + raise DataError('No outputs created.') + return rc + + +def rebot_cli(arguments=None, exit=True): + """Command line execution entry point for post-processing outputs. + + :param arguments: Command line options and arguments as a list of strings. + Starting from RF 3.1, defaults to ``sys.argv[1:]`` if not given. + :param exit: If ``True``, call ``sys.exit`` with the return code denoting + execution status, otherwise just return the rc. New in RF 3.0.1. + + Entry point used when post-processing outputs from the command line, but + can also be used by custom scripts. Especially useful if the script itself + needs to accept same arguments as accepted by Rebot, because the script can + just pass them forward directly along with the possible default values it + sets itself. + + Example:: + + from robot import rebot_cli + + rebot_cli(['--name', 'Example', '--log', 'NONE', 'o1.xml', 'o2.xml']) + + See also the :func:`rebot` function that allows setting options as keyword + arguments like ``name="Example"`` and generally has a richer API for + programmatic Rebot execution. + """ + if arguments is None: + arguments = sys.argv[1:] + return Rebot().execute_cli(arguments, exit=exit) + + +def rebot(*outputs, **options): + """Programmatic entry point for post-processing outputs. + + :param outputs: Paths to Robot Framework output files similarly + as when running the ``rebot`` command on the command line. + :param options: Options to configure processing outputs. Accepted + options are mostly same as normal command line options to the ``rebot`` + command. Option names match command line option long names without + hyphens so that, for example, ``--name`` becomes ``name``. + + The semantics related to passing options are exactly the same as with the + :func:`~robot.run.run` function. See its documentation for more details. + + Examples:: + + from robot import rebot + + rebot('path/to/output.xml') + with open('stdout.txt', 'w') as stdout: + rebot('o1.xml', 'o2.xml', name='Example', log=None, stdout=stdout) + + Equivalent command line usage:: + + rebot path/to/output.xml + rebot --name Example --log NONE o1.xml o2.xml > stdout.txt + """ + return Rebot().execute(*outputs, **options) + + +if __name__ == '__main__': + rebot_cli(sys.argv[1:]) diff --git a/robot/lib/python3.8/site-packages/robot/reporting/__init__.py b/robot/lib/python3.8/site-packages/robot/reporting/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2847b60a862d0e9baff754be1eec1fa1b7fa876c --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/reporting/__init__.py @@ -0,0 +1,29 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Implements report, log, output XML, and xUnit file generation. + +The public API of this package is the :class:`~.ResultWriter` class. It +can write result files based on XML output files on the file system, +as well as based on the result objects returned by +the :func:`~robot.result.resultbuilder.ExecutionResult` factory method or +an executed :class:`~robot.running.model.TestSuite`. + +It is highly recommended to use the public API via the :mod:`robot.api` package. + +This package is considered stable. +""" + +from .resultwriter import ResultWriter diff --git a/robot/lib/python3.8/site-packages/robot/reporting/expandkeywordmatcher.py b/robot/lib/python3.8/site-packages/robot/reporting/expandkeywordmatcher.py new file mode 100644 index 0000000000000000000000000000000000000000..b195569f0aa7f83f81e26b0219618ebf1b2d110f --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/reporting/expandkeywordmatcher.py @@ -0,0 +1,34 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import MultiMatcher, is_list_like + + +class ExpandKeywordMatcher(object): + + def __init__(self, expand_keywords): + self.matched_ids = [] + if not expand_keywords: + expand_keywords = [] + elif not is_list_like(expand_keywords): + expand_keywords = [expand_keywords] + names = [n[5:] for n in expand_keywords if n[:5].lower() == 'name:'] + tags = [p[4:] for p in expand_keywords if p[:4].lower() == 'tag:'] + self._match_name = MultiMatcher(names).match + self._match_tags = MultiMatcher(tags).match_any + + def match(self, kw): + if self._match_name(kw.name) or self._match_tags(kw.tags): + self.matched_ids.append(kw.id) diff --git a/robot/lib/python3.8/site-packages/robot/reporting/jsbuildingcontext.py b/robot/lib/python3.8/site-packages/robot/reporting/jsbuildingcontext.py new file mode 100644 index 0000000000000000000000000000000000000000..c942360312b87d4a5a6a57b1549055c54a9b8dff --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/reporting/jsbuildingcontext.py @@ -0,0 +1,109 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from contextlib import contextmanager +from os.path import exists, dirname + +from robot.output.loggerhelper import LEVELS +from robot.utils import (attribute_escape, get_link_path, html_escape, + html_format, is_string, is_unicode, timestamp_to_secs, + unic) + +from .expandkeywordmatcher import ExpandKeywordMatcher +from .stringcache import StringCache + + +class JsBuildingContext(object): + + def __init__(self, log_path=None, split_log=False, expand_keywords=None, + prune_input=False): + # log_path can be a custom object in unit tests + self._log_dir = dirname(log_path) if is_string(log_path) else None + self._split_log = split_log + self._prune_input = prune_input + self._strings = self._top_level_strings = StringCache() + self.basemillis = None + self.split_results = [] + self.min_level = 'NONE' + self._msg_links = {} + self._expand_matcher = ExpandKeywordMatcher(expand_keywords) \ + if expand_keywords else None + + def string(self, string, escape=True, attr=False): + if escape and string: + if not is_unicode(string): + string = unic(string) + string = (html_escape if not attr else attribute_escape)(string) + return self._strings.add(string) + + def html(self, string): + return self.string(html_format(string), escape=False) + + def relative_source(self, source): + rel_source = get_link_path(source, self._log_dir) \ + if self._log_dir and source and exists(source) else '' + return self.string(rel_source) + + def timestamp(self, time): + if not time: + return None + millis = int(timestamp_to_secs(time) * 1000) + if self.basemillis is None: + self.basemillis = millis + return millis - self.basemillis + + def message_level(self, level): + if LEVELS[level] < LEVELS[self.min_level]: + self.min_level = level + + def create_link_target(self, msg): + id = self._top_level_strings.add(msg.parent.id) + self._msg_links[self._link_key(msg)] = id + + def check_expansion(self, kw): + if self._expand_matcher is not None: + self._expand_matcher.match(kw) + + @property + def expand_keywords(self): + return self._expand_matcher.matched_ids if self._expand_matcher else None + + def link(self, msg): + return self._msg_links.get(self._link_key(msg)) + + def _link_key(self, msg): + return (msg.message, msg.level, msg.timestamp) + + @property + def strings(self): + return self._strings.dump() + + def start_splitting_if_needed(self, split=False): + if self._split_log and split: + self._strings = StringCache() + return True + return False + + def end_splitting(self, model): + self.split_results.append((model, self.strings)) + self._strings = self._top_level_strings + return len(self.split_results) + + @contextmanager + def prune_input(self, *items): + yield + if self._prune_input: + for item in items: + item.clear() diff --git a/robot/lib/python3.8/site-packages/robot/reporting/jsexecutionresult.py b/robot/lib/python3.8/site-packages/robot/reporting/jsexecutionresult.py new file mode 100644 index 0000000000000000000000000000000000000000..7d24df930d055beeecec6a0b324e9e61d476dd15 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/reporting/jsexecutionresult.py @@ -0,0 +1,106 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +from collections import OrderedDict + +from robot.utils import IRONPYTHON, PY_VERSION + +from .stringcache import StringIndex + +# http://ironpython.codeplex.com/workitem/31549 +if IRONPYTHON and PY_VERSION < (2, 7, 2): + int = long + + +class JsExecutionResult(object): + + def __init__(self, suite, statistics, errors, strings, basemillis=None, + split_results=None, min_level=None, expand_keywords=None): + self.suite = suite + self.strings = strings + self.min_level = min_level + self.data = self._get_data(statistics, errors, basemillis or 0, + expand_keywords) + self.split_results = split_results or [] + + def _get_data(self, statistics, errors, basemillis, expand_keywords): + return OrderedDict([ + ('stats', statistics), + ('errors', errors), + ('baseMillis', basemillis), + ('generated', int(time.time() * 1000) - basemillis), + ('expand_keywords', expand_keywords) + ]) + + def remove_data_not_needed_in_report(self): + self.data.pop('errors') + remover = _KeywordRemover() + self.suite = remover.remove_keywords(self.suite) + self.suite, self.strings \ + = remover.remove_unused_strings(self.suite, self.strings) + + +class _KeywordRemover(object): + + def remove_keywords(self, suite): + return self._remove_keywords_from_suite(suite) + + def _remove_keywords_from_suite(self, suite): + return suite[:6] + (self._remove_keywords_from_suites(suite[6]), + self._remove_keywords_from_tests(suite[7]), + (), suite[9]) + + def _remove_keywords_from_suites(self, suites): + return tuple(self._remove_keywords_from_suite(s) for s in suites) + + def _remove_keywords_from_tests(self, tests): + return tuple(self._remove_keywords_from_test(t) for t in tests) + + def _remove_keywords_from_test(self, test): + return test[:-1] + ((),) + + def remove_unused_strings(self, model, strings): + used = set(self._get_used_indices(model)) + remap = {} + strings = tuple(self._get_used_strings(strings, used, remap)) + model = tuple(self._remap_string_indices(model, remap)) + return model, strings + + def _get_used_indices(self, model): + for item in model: + if isinstance(item, StringIndex): + yield item + elif isinstance(item, tuple): + for i in self._get_used_indices(item): + yield i + + def _get_used_strings(self, strings, used_indices, remap): + offset = 0 + for index, string in enumerate(strings): + if index in used_indices: + remap[index] = index - offset + yield string + else: + offset += 1 + + def _remap_string_indices(self, model, remap): + for item in model: + if isinstance(item, StringIndex): + yield remap[item] + elif isinstance(item, tuple): + yield tuple(self._remap_string_indices(item, remap)) + else: + yield item diff --git a/robot/lib/python3.8/site-packages/robot/reporting/jsmodelbuilders.py b/robot/lib/python3.8/site-packages/robot/reporting/jsmodelbuilders.py new file mode 100644 index 0000000000000000000000000000000000000000..a3dd0b42a5c02c1c85fcfe753c2143964eda6eb8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/reporting/jsmodelbuilders.py @@ -0,0 +1,192 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.output import LEVELS + +from .jsbuildingcontext import JsBuildingContext +from .jsexecutionresult import JsExecutionResult + + +class JsModelBuilder(object): + + def __init__(self, log_path=None, split_log=False, expand_keywords=None, + prune_input_to_save_memory=False): + self._context = JsBuildingContext(log_path, split_log, expand_keywords, + prune_input_to_save_memory) + + def build_from(self, result_from_xml): + # Statistics must be build first because building suite may prune input. + return JsExecutionResult( + statistics=StatisticsBuilder().build(result_from_xml.statistics), + suite=SuiteBuilder(self._context).build(result_from_xml.suite), + errors=ErrorsBuilder(self._context).build(result_from_xml.errors), + strings=self._context.strings, + basemillis=self._context.basemillis, + split_results=self._context.split_results, + min_level=self._context.min_level, + expand_keywords=self._context.expand_keywords + ) + + +class _Builder(object): + _statuses = {'FAIL': 0, 'PASS': 1, 'NOT_RUN': 2} + + def __init__(self, context): + self._context = context + self._string = self._context.string + self._html = self._context.html + self._timestamp = self._context.timestamp + + def _get_status(self, item): + model = (self._statuses[item.status], + self._timestamp(item.starttime), + item.elapsedtime) + msg = getattr(item, 'message', '') + if not msg: + return model + elif msg.startswith('*HTML*'): + msg = self._string(msg[6:].lstrip(), escape=False) + else: + msg = self._string(msg) + return model + (msg,) + + def _build_keywords(self, kws, split=False): + splitting = self._context.start_splitting_if_needed(split) + model = tuple(self._build_keyword(k) for k in kws) + return model if not splitting else self._context.end_splitting(model) + + +class SuiteBuilder(_Builder): + + def __init__(self, context): + _Builder.__init__(self, context) + self._build_suite = self.build + self._build_test = TestBuilder(context).build + self._build_keyword = KeywordBuilder(context).build + + def build(self, suite): + with self._context.prune_input(suite.suites, suite.tests, suite.keywords): + stats = self._get_statistics(suite) # Must be done before pruning + return (self._string(suite.name, attr=True), + self._string(suite.source), + self._context.relative_source(suite.source), + self._html(suite.doc), + tuple(self._yield_metadata(suite)), + self._get_status(suite), + tuple(self._build_suite(s) for s in suite.suites), + tuple(self._build_test(t) for t in suite.tests), + tuple(self._build_keyword(k, split=True) for k in suite.keywords), + stats) + + def _yield_metadata(self, suite): + for name, value in suite.metadata.items(): + yield self._string(name) + yield self._html(value) + + def _get_statistics(self, suite): + stats = suite.statistics # Access property only once + return (stats.all.total, + stats.all.passed, + stats.critical.total, + stats.critical.passed) + + +class TestBuilder(_Builder): + + def __init__(self, context): + _Builder.__init__(self, context) + self._build_keyword = KeywordBuilder(context).build + + def build(self, test): + with self._context.prune_input(test.keywords): + return (self._string(test.name, attr=True), + self._string(test.timeout), + int(test.critical), + self._html(test.doc), + tuple(self._string(t) for t in test.tags), + self._get_status(test), + self._build_keywords(test.keywords, split=True)) + + +class KeywordBuilder(_Builder): + _types = {'kw': 0, 'setup': 1, 'teardown': 2, 'for': 3, 'foritem': 4} + + def __init__(self, context): + _Builder.__init__(self, context) + self._build_keyword = self.build + self._build_message = MessageBuilder(context).build + + def build(self, kw, split=False): + self._context.check_expansion(kw) + with self._context.prune_input(kw.messages, kw.keywords): + return (self._types[kw.type], + self._string(kw.kwname, attr=True), + self._string(kw.libname, attr=True), + self._string(kw.timeout), + self._html(kw.doc), + self._string(', '.join(kw.args)), + self._string(', '.join(kw.assign)), + self._string(', '.join(kw.tags)), + self._get_status(kw), + self._build_keywords(kw.keywords, split), + tuple(self._build_message(m) for m in kw.messages)) + + +class MessageBuilder(_Builder): + + def build(self, msg): + if msg.level in ('WARN','ERROR'): + self._context.create_link_target(msg) + self._context.message_level(msg.level) + return self._build(msg) + + def _build(self, msg): + return (self._timestamp(msg.timestamp), + LEVELS[msg.level], + self._string(msg.html_message, escape=False)) + + +class StatisticsBuilder(object): + + def build(self, statistics): + return (self._build_stats(statistics.total), + self._build_stats(statistics.tags), + self._build_stats(statistics.suite, exclude_empty=False)) + + def _build_stats(self, stats, exclude_empty=True): + return tuple(stat.get_attributes(include_label=True, + include_elapsed=True, + exclude_empty=exclude_empty, + html_escape=True) + for stat in stats) + + +class ErrorsBuilder(_Builder): + + def __init__(self, context): + _Builder.__init__(self, context) + self._build_message = ErrorMessageBuilder(context).build + + def build(self, errors): + with self._context.prune_input(errors.messages): + return tuple(self._build_message(msg) for msg in errors) + + +class ErrorMessageBuilder(MessageBuilder): + + def build(self, msg): + model = self._build(msg) + link = self._context.link(msg) + return model if link is None else model + (link,) diff --git a/robot/lib/python3.8/site-packages/robot/reporting/jswriter.py b/robot/lib/python3.8/site-packages/robot/reporting/jswriter.py new file mode 100644 index 0000000000000000000000000000000000000000..31fc60a5fc311cf46bbab88ffed794e673540119 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/reporting/jswriter.py @@ -0,0 +1,108 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.htmldata import JsonWriter + + +class JsResultWriter(object): + _output_attr = 'window.output' + _settings_attr = 'window.settings' + _suite_key = 'suite' + _strings_key = 'strings' + + def __init__(self, output, + start_block='\n', + split_threshold=9500): + writer = JsonWriter(output, separator=end_block+start_block) + self._write = writer.write + self._write_json = writer.write_json + self._start_block = start_block + self._end_block = end_block + self._split_threshold = split_threshold + + def write(self, result, settings): + self._start_output_block() + self._write_suite(result.suite) + self._write_strings(result.strings) + self._write_data(result.data) + self._write_settings_and_end_output_block(settings) + + def _start_output_block(self): + self._write(self._start_block, postfix='', separator=False) + self._write('%s = {}' % self._output_attr) + + def _write_suite(self, suite): + writer = SuiteWriter(self._write_json, self._split_threshold) + writer.write(suite, self._output_var(self._suite_key)) + + def _write_strings(self, strings): + variable = self._output_var(self._strings_key) + self._write('%s = []' % variable) + prefix = '%s = %s.concat(' % (variable, variable) + postfix = ');\n' + threshold = self._split_threshold + for index in range(0, len(strings), threshold): + self._write_json(prefix, strings[index:index+threshold], postfix) + + def _write_data(self, data): + for key in data: + self._write_json('%s = ' % self._output_var(key), data[key]) + + def _write_settings_and_end_output_block(self, settings): + self._write_json('%s = ' % self._settings_attr, settings, + separator=False) + self._write(self._end_block, postfix='', separator=False) + + def _output_var(self, key): + return '%s["%s"]' % (self._output_attr, key) + + +class SuiteWriter(object): + + def __init__(self, write_json, split_threshold): + self._write_json = write_json + self._split_threshold = split_threshold + + def write(self, suite, variable): + mapping = {} + self._write_parts_over_threshold(suite, mapping) + self._write_json('%s = ' % variable, suite, mapping=mapping) + + def _write_parts_over_threshold(self, data, mapping): + if not isinstance(data, tuple): + return 1 + not_written = 1 + sum(self._write_parts_over_threshold(item, mapping) + for item in data) + if not_written > self._split_threshold: + self._write_part(data, mapping) + return 1 + return not_written + + def _write_part(self, data, mapping): + part_name = 'window.sPart%d' % len(mapping) + self._write_json('%s = ' % part_name, data, mapping=mapping) + mapping[data] = part_name + + +class SplitLogWriter(object): + + def __init__(self, output): + self._writer = JsonWriter(output) + + def write(self, keywords, strings, index, notify): + self._writer.write_json('window.keywords%d = ' % index, keywords) + self._writer.write_json('window.strings%d = ' % index, strings) + self._writer.write('window.fileLoading.notify("%s")' % notify) diff --git a/robot/lib/python3.8/site-packages/robot/reporting/logreportwriters.py b/robot/lib/python3.8/site-packages/robot/reporting/logreportwriters.py new file mode 100644 index 0000000000000000000000000000000000000000..0536bc40784cd6f8e5c16e5840fdc1e45c5f44b6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/reporting/logreportwriters.py @@ -0,0 +1,73 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from os.path import basename, splitext + +from robot.htmldata import HtmlFileWriter, ModelWriter, LOG, REPORT +from robot.utils import file_writer, is_string + +from .jswriter import JsResultWriter, SplitLogWriter + + +class _LogReportWriter(object): + usage = None + + def __init__(self, js_model): + self._js_model = js_model + + def _write_file(self, path, config, template): + outfile = file_writer(path, usage=self.usage) \ + if is_string(path) else path # unit test hook + with outfile: + model_writer = RobotModelWriter(outfile, self._js_model, config) + writer = HtmlFileWriter(outfile, model_writer) + writer.write(template) + + +class LogWriter(_LogReportWriter): + usage = 'log' + + def write(self, path, config): + self._write_file(path, config, LOG) + if self._js_model.split_results: + self._write_split_logs(splitext(path)[0]) + + def _write_split_logs(self, base): + for index, (keywords, strings) in enumerate(self._js_model.split_results, + start=1): + self._write_split_log(index, keywords, strings, '%s-%d.js' % (base, index)) + + def _write_split_log(self, index, keywords, strings, path): + with file_writer(path, usage=self.usage) as outfile: + writer = SplitLogWriter(outfile) + writer.write(keywords, strings, index, basename(path)) + + +class ReportWriter(_LogReportWriter): + usage = 'report' + + def write(self, path, config): + self._write_file(path, config, REPORT) + + +class RobotModelWriter(ModelWriter): + + def __init__(self, output, model, config): + self._output = output + self._model = model + self._config = config + + def write(self, line): + JsResultWriter(self._output).write(self._model, self._config) diff --git a/robot/lib/python3.8/site-packages/robot/reporting/outputwriter.py b/robot/lib/python3.8/site-packages/robot/reporting/outputwriter.py new file mode 100644 index 0000000000000000000000000000000000000000..4ba1ea5f0430da2c328827db4bca2926de2ad02e --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/reporting/outputwriter.py @@ -0,0 +1,38 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.output.xmllogger import XmlLogger + + +class OutputWriter(XmlLogger): + + def __init__(self, output, rpa=False): + XmlLogger.__init__(self, output, rpa=rpa, generator='Rebot') + + def start_message(self, msg): + self._write_message(msg) + + def visit_keyword(self, kw): + self.start_keyword(kw) + for child in kw.children: + child.visit(self) + self.end_keyword(kw) + + def close(self): + self._writer.end('robot') + self._writer.close() + + def end_result(self, result): + self.close() diff --git a/robot/lib/python3.8/site-packages/robot/reporting/resultwriter.py b/robot/lib/python3.8/site-packages/robot/reporting/resultwriter.py new file mode 100644 index 0000000000000000000000000000000000000000..dc5f8f4a0326eef3e77ee06c3b0a4c7bd01b0125 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/reporting/resultwriter.py @@ -0,0 +1,140 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.conf import RebotSettings +from robot.errors import DataError +from robot.model import ModelModifier +from robot.output import LOGGER +from robot.result import ExecutionResult, Result +from robot.utils import unic + +from .jsmodelbuilders import JsModelBuilder +from .logreportwriters import LogWriter, ReportWriter +from .xunitwriter import XUnitWriter + + +class ResultWriter(object): + """A class to create log, report, output XML and xUnit files. + + :param sources: Either one :class:`~robot.result.executionresult.Result` + object, or one or more paths to existing output XML files. + + By default writes ``report.html`` and ``log.html``, but no output XML + or xUnit files. Custom file names can be given and results disabled + or enabled using ``settings`` or ``options`` passed to the + :meth:`write_results` method. The latter is typically more convenient:: + + writer = ResultWriter(result) + writer.write_results(report='custom.html', log=None, xunit='xunit.xml') + """ + + def __init__(self, *sources): + self._sources = sources + + def write_results(self, settings=None, **options): + """Writes results based on the given ``settings`` or ``options``. + + :param settings: :class:`~robot.conf.settings.RebotSettings` object + to configure result writing. + :param options: Used to construct new + :class:`~robot.conf.settings.RebotSettings` object if ``settings`` + are not given. + """ + settings = settings or RebotSettings(options) + results = Results(settings, *self._sources) + if settings.output: + self._write_output(results.result, settings.output) + if settings.xunit: + self._write_xunit(results.result, settings.xunit, + settings.xunit_skip_noncritical) + if settings.log: + config = dict(settings.log_config, + minLevel=results.js_result.min_level) + self._write_log(results.js_result, settings.log, config) + if settings.report: + results.js_result.remove_data_not_needed_in_report() + self._write_report(results.js_result, settings.report, + settings.report_config) + return results.return_code + + def _write_output(self, result, path): + self._write('Output', result.save, path) + + def _write_xunit(self, result, path, skip_noncritical): + self._write('XUnit', XUnitWriter(result, skip_noncritical).write, path) + + def _write_log(self, js_result, path, config): + self._write('Log', LogWriter(js_result).write, path, config) + + def _write_report(self, js_result, path, config): + self._write('Report', ReportWriter(js_result).write, path, config) + + def _write(self, name, writer, path, *args): + try: + writer(path, *args) + except DataError as err: + LOGGER.error(err.message) + else: + LOGGER.output_file(name, path) + + +class Results(object): + + def __init__(self, settings, *sources): + self._settings = settings + self._sources = sources + if len(sources) == 1 and isinstance(sources[0], Result): + self._result = sources[0] + self._prune = False + self.return_code = self._result.return_code + else: + self._result = None + self._prune = True + self.return_code = -1 + self._js_result = None + + @property + def result(self): + if self._result is None: + include_keywords = bool(self._settings.log or self._settings.output) + flattened = self._settings.flatten_keywords + self._result = ExecutionResult(include_keywords=include_keywords, + flattened_keywords=flattened, + merge=self._settings.merge, + rpa=self._settings.rpa, + *self._sources) + if self._settings.rpa is None: + self._settings.rpa = self._result.rpa + modifier = ModelModifier(self._settings.pre_rebot_modifiers, + self._settings.process_empty_suite, + LOGGER) + self._result.suite.visit(modifier) + self._result.configure(self._settings.status_rc, + self._settings.suite_config, + self._settings.statistics_config) + self.return_code = self._result.return_code + return self._result + + @property + def js_result(self): + if self._js_result is None: + builder = JsModelBuilder(log_path=self._settings.log, + split_log=self._settings.split_log, + expand_keywords=self._settings.expand_keywords, + prune_input_to_save_memory=self._prune) + self._js_result = builder.build_from(self.result) + if self._prune: + self._result = None + return self._js_result diff --git a/robot/lib/python3.8/site-packages/robot/reporting/stringcache.py b/robot/lib/python3.8/site-packages/robot/reporting/stringcache.py new file mode 100644 index 0000000000000000000000000000000000000000..3b4f2ca490757d23513b1235b87ab71df9c11489 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/reporting/stringcache.py @@ -0,0 +1,54 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import OrderedDict + +from robot.utils import compress_text + + +class StringIndex(int): + pass + + +class StringCache(object): + _compress_threshold = 80 + _use_compressed_threshold = 1.1 + _zero_index = StringIndex(0) + + def __init__(self): + self._cache = OrderedDict({'*': self._zero_index}) + + def add(self, text): + if not text: + return self._zero_index + text = self._encode(text) + if text not in self._cache: + self._cache[text] = StringIndex(len(self._cache)) + return self._cache[text] + + def _encode(self, text): + raw = self._raw(text) + if raw in self._cache or len(raw) < self._compress_threshold: + return raw + compressed = compress_text(text) + if len(compressed) * self._use_compressed_threshold < len(raw): + return compressed + return raw + + def _raw(self, text): + return '*'+text + + def dump(self): + return tuple(self._cache) diff --git a/robot/lib/python3.8/site-packages/robot/reporting/xunitwriter.py b/robot/lib/python3.8/site-packages/robot/reporting/xunitwriter.py new file mode 100644 index 0000000000000000000000000000000000000000..66464acca23455fe47ab89380583028952a73e5e --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/reporting/xunitwriter.py @@ -0,0 +1,104 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import division + +from robot.result import ResultVisitor +from robot.utils import XmlWriter + + +class XUnitWriter(object): + + def __init__(self, execution_result, skip_noncritical): + self._execution_result = execution_result + self._skip_noncritical = skip_noncritical + + def write(self, output): + xml_writer = XmlWriter(output, usage='xunit') + writer = XUnitFileWriter(xml_writer, self._skip_noncritical) + self._execution_result.visit(writer) + + +class XUnitFileWriter(ResultVisitor): + """Provides an xUnit-compatible result file. + + Attempts to adhere to the de facto schema guessed by Peter Reilly, see: + http://marc.info/?l=ant-dev&m=123551933508682 + """ + + def __init__(self, xml_writer, skip_noncritical=False): + self._writer = xml_writer + self._root_suite = None + self._skip_noncritical = skip_noncritical + + def start_suite(self, suite): + if self._root_suite: + return + self._root_suite = suite + tests, failures, skipped = self._get_stats(suite.statistics) + attrs = {'name': suite.name, + 'tests': tests, + 'errors': '0', + 'failures': failures, + 'skipped': skipped, + 'time': self._time_as_seconds(suite.elapsedtime)} + self._writer.start('testsuite', attrs) + + def _get_stats(self, statistics): + if self._skip_noncritical: + failures = statistics.critical.failed + skipped = statistics.all.total - statistics.critical.total + else: + failures = statistics.all.failed + skipped = 0 + return str(statistics.all.total), str(failures), str(skipped) + + def end_suite(self, suite): + if suite is self._root_suite: + self._writer.end('testsuite') + + def visit_test(self, test): + self._writer.start('testcase', + {'classname': test.parent.longname, + 'name': test.name, + 'time': self._time_as_seconds(test.elapsedtime)}) + if self._skip_noncritical and not test.critical: + self._skip_test(test) + elif not test.passed: + self._fail_test(test) + self._writer.end('testcase') + + def _skip_test(self, test): + self._writer.element('skipped', '%s: %s' % (test.status, test.message) + if test.message else test.status) + + def _fail_test(self, test): + self._writer.element('failure', attrs={'message': test.message, + 'type': 'AssertionError'}) + + def _time_as_seconds(self, millis): + return '{:.3f}'.format(millis / 1000) + + def visit_keyword(self, kw): + pass + + def visit_statistics(self, stats): + pass + + def visit_errors(self, errors): + pass + + def end_result(self, result): + self._writer.close() diff --git a/robot/lib/python3.8/site-packages/robot/result/__init__.py b/robot/lib/python3.8/site-packages/robot/result/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..faaef35d8392347a09649fe372a40212fc9d4960 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/__init__.py @@ -0,0 +1,47 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Implements parsing execution results from XML output files. + +The main public API of this package consists of the :func:`~.ExecutionResult` +factory method, that returns :class:`~.Result` objects, and of the +:class:`~.ResultVisitor` abstract class, that eases further processing +the results. + +The model objects in the :mod:`~.model` module can also be considered to be +part of the public API, because they can be found inside the :class:`~.Result` +object. They can also be inspected and modified as part of the normal test +execution by `pre-Rebot modifiers`__ and `listeners`__. + +It is highly recommended to import the public entry-points via the +:mod:`robot.api` package like in the example below. In those rare cases +where the aforementioned model objects are needed directly, they can be +imported from this package. + +This package is considered stable. + +Example +------- + +.. literalinclude:: /../../doc/api/code_examples/check_test_times.py + +__ http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#programmatic-modification-of-results +__ http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#listener-interface +""" + +from .executionresult import Result +from .model import Message, Keyword, TestCase, TestSuite +from .resultbuilder import ExecutionResult, ExecutionResultBuilder +from .visitor import ResultVisitor diff --git a/robot/lib/python3.8/site-packages/robot/result/configurer.py b/robot/lib/python3.8/site-packages/robot/result/configurer.py new file mode 100644 index 0000000000000000000000000000000000000000..9250e775167d70d5b00ab157e104bd4b6df4396c --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/configurer.py @@ -0,0 +1,76 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot import model +from robot.utils import is_string, secs_to_timestamp, timestamp_to_secs + + +class SuiteConfigurer(model.SuiteConfigurer): + """Result suite configured. + + Calls suite's + :meth:`~robot.result.testsuite.TestSuite.remove_keywords`, + :meth:`~robot.result.testsuite.TestSuite.filter_messages` and + :meth:`~robot.result.testsuite.TestSuite.set_criticality` methods + and sets its start and end time based on the given named parameters. + + ``base_config`` is forwarded to + :class:`robot.model.SuiteConfigurer ` + that will do further configuration based on them. + """ + + def __init__(self, remove_keywords=None, log_level=None, start_time=None, + end_time=None, critical_tags=None, non_critical_tags=None, + **base_config): + model.SuiteConfigurer.__init__(self, **base_config) + self.remove_keywords = self._get_remove_keywords(remove_keywords) + self.log_level = log_level + self.start_time = self._get_time(start_time) + self.end_time = self._get_time(end_time) + self.critical_tags = critical_tags + self.non_critical_tags = non_critical_tags + + def _get_remove_keywords(self, value): + if value is None: + return [] + if is_string(value): + return [value] + return value + + def _get_time(self, timestamp): + if not timestamp: + return None + try: + secs = timestamp_to_secs(timestamp, seps=' :.-_') + except ValueError: + return None + return secs_to_timestamp(secs, millis=True) + + def visit_suite(self, suite): + model.SuiteConfigurer.visit_suite(self, suite) + self._remove_keywords(suite) + self._set_times(suite) + suite.filter_messages(self.log_level) + suite.set_criticality(self.critical_tags, self.non_critical_tags) + + def _remove_keywords(self, suite): + for how in self.remove_keywords: + suite.remove_keywords(how) + + def _set_times(self, suite): + if self.start_time: + suite.starttime = self.start_time + if self.end_time: + suite.endtime = self.end_time diff --git a/robot/lib/python3.8/site-packages/robot/result/executionerrors.py b/robot/lib/python3.8/site-packages/robot/result/executionerrors.py new file mode 100644 index 0000000000000000000000000000000000000000..0446ae2cc36ad18df1c62fa3c4763bb178c6aa7e --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/executionerrors.py @@ -0,0 +1,51 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.model import ItemList +from robot.utils import setter + +from .model import Message + + +class ExecutionErrors(object): + """Represents errors occurred during the execution of tests. + + An error might be, for example, that importing a library has failed. + """ + message_class = Message + + def __init__(self, messages=None): + #: A :class:`list-like object ` of + #: :class:`~robot.model.message.Message` instances. + self.messages = messages + + @setter + def messages(self, msgs): + return ItemList(self.message_class, items=msgs) + + def add(self, other): + self.messages.extend(other.messages) + + def visit(self, visitor): + visitor.visit_errors(self) + + def __iter__(self): + return iter(self.messages) + + def __len__(self): + return len(self.messages) + + def __getitem__(self, index): + return self.messages[index] diff --git a/robot/lib/python3.8/site-packages/robot/result/executionresult.py b/robot/lib/python3.8/site-packages/robot/result/executionresult.py new file mode 100644 index 0000000000000000000000000000000000000000..1fb5e3b18baaa77e83d1733aefaaae92b54b6f5d --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/executionresult.py @@ -0,0 +1,152 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.model import Statistics + +from .executionerrors import ExecutionErrors +from .model import TestSuite + + +class Result(object): + """Test execution results. + + Can be created based on XML output files using the + :func:`~.resultbuilder.ExecutionResult` + factory method. Also returned by the + :meth:`robot.running.TestSuite.run ` + method. + """ + + def __init__(self, source=None, root_suite=None, errors=None, rpa=None): + #: Path to the XML file where results are read from. + self.source = source + #: Hierarchical execution results as a + #: :class:`~.result.model.TestSuite` object. + self.suite = root_suite or TestSuite() + #: Execution errors as an + #: :class:`~.executionerrors.ExecutionErrors` object. + self.errors = errors or ExecutionErrors() + self.generated_by_robot = True + self._status_rc = True + self._stat_config = {} + self.rpa = rpa + + @property + def statistics(self): + """Test execution statistics. + + Statistics are an instance of + :class:`~robot.model.statistics.Statistics` that is created based + on the contained ``suite`` and possible + :func:`configuration `. + + Statistics are created every time this property is accessed. Saving + them to a variable is thus often a good idea to avoid re-creating + them unnecessarily:: + + from robot.api import ExecutionResult + + result = ExecutionResult('output.xml') + result.configure(stat_config={'suite_stat_level': 2, + 'tag_stat_combine': 'tagANDanother'}) + stats = result.statistics + print stats.total.critical.failed + print stats.total.critical.passed + print stats.tags.combined[0].total + """ + return Statistics(self.suite, rpa=self.rpa, **self._stat_config) + + @property + def return_code(self): + """Return code (integer) of test execution. + + By default returns the number of failed critical tests (max 250), + but can be :func:`configured ` to always return 0. + """ + if self._status_rc: + return min(self.suite.statistics.critical.failed, 250) + return 0 + + def configure(self, status_rc=True, suite_config=None, stat_config=None): + """Configures the result object and objects it contains. + + :param status_rc: If set to ``False``, :attr:`return_code` always + returns 0. + :param suite_config: A dictionary of configuration options passed + to :meth:`~.result.testsuite.TestSuite.configure` method of + the contained ``suite``. + :param stat_config: A dictionary of configuration options used when + creating :attr:`statistics`. + """ + if suite_config: + self.suite.configure(**suite_config) + self._status_rc = status_rc + self._stat_config = stat_config or {} + + def save(self, path=None): + """Save results as a new output XML file. + + :param path: Path to save results to. If omitted, overwrites the + original file. + """ + from robot.reporting.outputwriter import OutputWriter + self.visit(OutputWriter(path or self.source, rpa=self.rpa)) + + def visit(self, visitor): + """An entry point to visit the whole result object. + + :param visitor: An instance of :class:`~.visitor.ResultVisitor`. + + Visitors can gather information, modify results, etc. See + :mod:`~robot.result` package for a simple usage example. + + Notice that it is also possible to call :meth:`result.suite.visit + ` if there is no need to + visit the contained ``statistics`` or ``errors``. + """ + visitor.visit_result(self) + + def handle_suite_teardown_failures(self): + """Internal usage only.""" + if self.generated_by_robot: + self.suite.handle_suite_teardown_failures() + + def set_execution_mode(self, other): + """Set execution mode based on other result. Internal usage only.""" + if other.rpa is None: + pass + elif self.rpa is None: + self.rpa = other.rpa + elif self.rpa is not other.rpa: + this, that = ('task', 'test') if other.rpa else ('test', 'task') + raise DataError("Conflicting execution modes. File '%s' has %ss " + "but files parsed earlier have %ss. Use '--rpa' " + "or '--norpa' options to set the execution mode " + "explicitly." % (other.source, this, that)) + + +class CombinedResult(Result): + """Combined results of multiple test executions.""" + + def __init__(self, results=None): + Result.__init__(self) + for result in results or (): + self.add_result(result) + + def add_result(self, other): + self.set_execution_mode(other) + self.suite.suites.append(other.suite) + self.errors.add(other.errors) diff --git a/robot/lib/python3.8/site-packages/robot/result/flattenkeywordmatcher.py b/robot/lib/python3.8/site-packages/robot/result/flattenkeywordmatcher.py new file mode 100644 index 0000000000000000000000000000000000000000..d1e2352c8c1d050bda21c3b504ca14fb62081874 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/flattenkeywordmatcher.py @@ -0,0 +1,77 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.model import TagPatterns +from robot.utils import MultiMatcher, is_list_like, py2to3 + + +def validate_flatten_keyword(options): + for opt in options: + low = opt.lower() + if not (low in ('for', 'foritem') or + low.startswith('name:') or + low.startswith('tag:')): + raise DataError("Expected 'FOR', 'FORITEM', 'TAG:', or " + "'NAME:' but got '%s'." % opt) + + +@py2to3 +class FlattenByTypeMatcher(object): + + def __init__(self, flatten): + if not is_list_like(flatten): + flatten = [flatten] + flatten = [f.lower() for f in flatten] + self._types = [f for f in flatten if f in ('for', 'foritem')] + + def match(self, kwtype): + return kwtype in self._types + + def __nonzero__(self): + return bool(self._types) + + +@py2to3 +class FlattenByNameMatcher(object): + + def __init__(self, flatten): + if not is_list_like(flatten): + flatten = [flatten] + names = [n[5:] for n in flatten if n[:5].lower() == 'name:'] + self._matcher = MultiMatcher(names) + + def match(self, kwname, libname=None): + name = '%s.%s' % (libname, kwname) if libname else kwname + return self._matcher.match(name) + + def __nonzero__(self): + return bool(self._matcher) + + +@py2to3 +class FlattenByTagMatcher(object): + + def __init__(self, flatten): + if not is_list_like(flatten): + flatten = [flatten] + patterns = [p[4:] for p in flatten if p[:4].lower() == 'tag:'] + self._matcher = TagPatterns(patterns) + + def match(self, kwtags): + return self._matcher.match(kwtags) + + def __nonzero__(self): + return bool(self._matcher) diff --git a/robot/lib/python3.8/site-packages/robot/result/keywordremover.py b/robot/lib/python3.8/site-packages/robot/result/keywordremover.py new file mode 100644 index 0000000000000000000000000000000000000000..0cc012b4b638b34bcdde6c797458e3af83434890 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/keywordremover.py @@ -0,0 +1,164 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.model import SuiteVisitor, TagPattern +from robot.utils import Matcher, plural_or_not + + +def KeywordRemover(how): + upper = how.upper() + if upper.startswith('NAME:'): + return ByNameKeywordRemover(pattern=how[5:]) + if upper.startswith('TAG:'): + return ByTagKeywordRemover(pattern=how[4:]) + try: + return {'ALL': AllKeywordsRemover, + 'PASSED': PassedKeywordRemover, + 'FOR': ForLoopItemsRemover, + 'WUKS': WaitUntilKeywordSucceedsRemover}[upper]() + except KeyError: + raise DataError("Expected 'ALL', 'PASSED', 'NAME:', " + "'TAG:', 'FOR', or 'WUKS' but got '%s'." % how) + + +class _KeywordRemover(SuiteVisitor): + _message = 'Keyword data removed using --RemoveKeywords option.' + + def __init__(self): + self._removal_message = RemovalMessage(self._message) + + def _clear_content(self, kw): + kw.keywords = [] + kw.messages = [] + self._removal_message.set(kw) + + def _failed_or_warning_or_error(self, item): + return not item.passed or self._warning_or_error(item) + + def _warning_or_error(self, item): + finder = WarningAndErrorFinder() + item.visit(finder) + return finder.found + + +class AllKeywordsRemover(_KeywordRemover): + + def visit_keyword(self, keyword): + self._clear_content(keyword) + + +class PassedKeywordRemover(_KeywordRemover): + + def start_suite(self, suite): + if not suite.statistics.all.failed: + for keyword in suite.keywords: + if not self._warning_or_error(keyword): + self._clear_content(keyword) + + def visit_test(self, test): + if not self._failed_or_warning_or_error(test): + for keyword in test.keywords: + self._clear_content(keyword) + + def visit_keyword(self, keyword): + pass + + +class ByNameKeywordRemover(_KeywordRemover): + + def __init__(self, pattern): + _KeywordRemover.__init__(self) + self._matcher = Matcher(pattern, ignore='_') + + def start_keyword(self, kw): + if self._matcher.match(kw.name) and not self._warning_or_error(kw): + self._clear_content(kw) + + +class ByTagKeywordRemover(_KeywordRemover): + + def __init__(self, pattern): + _KeywordRemover.__init__(self) + self._pattern = TagPattern(pattern) + + def start_keyword(self, kw): + if self._pattern.match(kw.tags) and not self._warning_or_error(kw): + self._clear_content(kw) + + +class ForLoopItemsRemover(_KeywordRemover): + _message = '%d passing step%s removed using --RemoveKeywords option.' + + def start_keyword(self, kw): + if kw.type == kw.FOR_LOOP_TYPE: + before = len(kw.keywords) + kw.keywords = self._remove_keywords(kw.keywords) + self._removal_message.set_if_removed(kw, before) + + def _remove_keywords(self, keywords): + return [kw for kw in keywords + if self._failed_or_warning_or_error(kw) or kw is keywords[-1]] + + +class WaitUntilKeywordSucceedsRemover(_KeywordRemover): + _message = '%d failing step%s removed using --RemoveKeywords option.' + + def start_keyword(self, kw): + if kw.name == 'BuiltIn.Wait Until Keyword Succeeds' and kw.keywords: + before = len(kw.keywords) + kw.keywords = self._remove_keywords(list(kw.keywords)) + self._removal_message.set_if_removed(kw, before) + + def _remove_keywords(self, keywords): + include_from_end = 2 if keywords[-1].passed else 1 + return self._kws_with_warnings(keywords[:-include_from_end]) \ + + keywords[-include_from_end:] + + def _kws_with_warnings(self, keywords): + return [kw for kw in keywords if self._warning_or_error(kw)] + + +class WarningAndErrorFinder(SuiteVisitor): + + def __init__(self): + self.found = False + + def start_suite(self, suite): + return not self.found + + def start_test(self, test): + return not self.found + + def start_keyword(self, keyword): + return not self.found + + def visit_message(self, msg): + if msg.level in ('WARN', 'ERROR'): + self.found = True + + +class RemovalMessage(object): + + def __init__(self, message): + self._message = message + + def set_if_removed(self, kw, len_before): + removed = len_before - len(kw.keywords) + if removed: + self.set(kw, self._message % (removed, plural_or_not(removed))) + + def set(self, kw, message=None): + kw.doc = ('%s\n\n_%s_' % (kw.doc, message or self._message)).strip() diff --git a/robot/lib/python3.8/site-packages/robot/result/merger.py b/robot/lib/python3.8/site-packages/robot/result/merger.py new file mode 100644 index 0000000000000000000000000000000000000000..3c796534b999df77d5adb29788b016641f949a82 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/merger.py @@ -0,0 +1,132 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.model import SuiteVisitor +from robot.utils import html_escape + + +class Merger(SuiteVisitor): + + def __init__(self, result, rpa=False): + self.result = result + self.current = None + self.rpa = rpa + + def merge(self, merged): + self.result.set_execution_mode(merged) + merged.suite.visit(self) + self.result.errors.add(merged.errors) + + def start_suite(self, suite): + try: + self.current = self._find_suite(self.current, suite.name) + except IndexError: + suite.message = self._create_add_message(suite, suite=True) + self.current.suites.append(suite) + return False + + def _find_suite(self, parent, name): + if not parent: + suite = self._find_root(name) + else: + suite = self._find(parent.suites, name) + suite.starttime = suite.endtime = None + return suite + + def _find_root(self, name): + root = self.result.suite + if root.name != name: + raise DataError("Cannot merge outputs containing different root " + "suites. Original suite is '%s' and merged is " + "'%s'." % (root.name, name)) + return root + + def _find(self, items, name): + for item in items: + if item.name == name: + return item + raise IndexError + + def end_suite(self, suite): + self.current = self.current.parent + + def visit_test(self, test): + try: + old = self._find(self.current.tests, test.name) + except IndexError: + test.message = self._create_add_message(test) + self.current.tests.append(test) + else: + test.message = self._create_merge_message(test, old) + index = self.current.tests.index(old) + self.current.tests[index] = test + + def _create_add_message(self, item, suite=False): + if suite: + item_type = 'Suite' + elif self.rpa: + item_type = 'Task' + else: + item_type = 'Test' + prefix = '*HTML* %s added from merged output.' % item_type + if not item.message: + return prefix + return ''.join([prefix, '


', self._html_escape(item.message)]) + + def _html_escape(self, message): + if message.startswith('*HTML*'): + return message[6:].lstrip() + else: + return html_escape(message) + + def _create_merge_message(self, new, old): + header = ('*HTML* ' + '%s has been re-executed and results merged.' + '' % ('Test' if not self.rpa else 'Task')) + return ''.join([ + header, + '
', + self._format_status_and_message('New', new), + '
', + self._format_old_status_and_message(old, header) + ]) + + def _format_status_and_message(self, state, test): + message = '%s %s
' % (self._status_header(state), + self._status_text(test.status)) + if test.message: + message += '%s %s
' % (self._message_header(state), + self._html_escape(test.message)) + return message + + def _status_header(self, state): + return '%s status:' % (state.lower(), state) + + def _status_text(self, status): + return '%s' % (status.lower(), status) + + def _message_header(self, state): + return '%s message:' % (state.lower(), state) + + def _format_old_status_and_message(self, test, merge_header): + if not test.message.startswith(merge_header): + return self._format_status_and_message('Old', test) + status_and_message = test.message.split('
', 1)[1] + return ( + status_and_message + .replace(self._status_header('New'), self._status_header('Old')) + .replace(self._message_header('New'), self._message_header('Old')) + ) diff --git a/robot/lib/python3.8/site-packages/robot/result/messagefilter.py b/robot/lib/python3.8/site-packages/robot/result/messagefilter.py new file mode 100644 index 0000000000000000000000000000000000000000..97838ba34c0713388f6a77930b8dbd49d779544d --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/messagefilter.py @@ -0,0 +1,28 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.output.loggerhelper import IsLogged + +from robot.model import SuiteVisitor + + +class MessageFilter(SuiteVisitor): + + def __init__(self, loglevel): + self._is_logged = IsLogged(loglevel or 'TRACE') + + def start_keyword(self, keyword): + keyword.messages = [msg for msg in keyword.messages + if self._is_logged(msg.level)] diff --git a/robot/lib/python3.8/site-packages/robot/result/model.py b/robot/lib/python3.8/site-packages/robot/result/model.py new file mode 100644 index 0000000000000000000000000000000000000000..35cb8bb5363cde7fb8e2f96b7f55518008a55d73 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/model.py @@ -0,0 +1,298 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module implementing result related model objects. + +During test execution these objects are created internally by various runners. +At that time they can inspected and modified by listeners__. + +When results are parsed from XML output files after execution to be able to +create logs and reports, these objects are created by the +:func:`~.resultbuilder.ExecutionResult` factory method. +At that point they can be inspected and modified by `pre-Rebot modifiers`__. + +The :func:`~.resultbuilder.ExecutionResult` factory method can also be used +by custom scripts and tools. In such usage it is often easiest to inspect and +modify these objects using the :mod:`visitor interface `. + +__ http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#listener-interface +__ http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#programmatic-modification-of-results + +""" + +from itertools import chain + +from robot.model import TotalStatisticsBuilder, Criticality +from robot import model, utils + +from .configurer import SuiteConfigurer +from .messagefilter import MessageFilter +from .keywordremover import KeywordRemover +from .suiteteardownfailed import (SuiteTeardownFailureHandler, + SuiteTeardownFailed) + + +# TODO: Should remove model.Message altogether and just implement the whole +# thing here. Additionally model.Keyword should not have `message_class` at +# all or it should be None. + +class Message(model.Message): + """Represents a single log message. + + See the base class for documentation of attributes not documented here. + """ + __slots__ = [] + + +class Keyword(model.Keyword): + """Represents results of a single keyword. + + See the base class for documentation of attributes not documented here. + """ + __slots__ = ['kwname', 'libname', 'status', 'starttime', 'endtime', 'message'] + message_class = Message + + def __init__(self, kwname='', libname='', doc='', args=(), assign=(), + tags=(), timeout=None, type='kw', status='FAIL', + starttime=None, endtime=None): + model.Keyword.__init__(self, '', doc, args, assign, tags, timeout, type) + #: Name of the keyword without library or resource name. + self.kwname = kwname or '' + #: Name of the library or resource containing this keyword. + self.libname = libname or '' + #: Execution status as a string. Typically ``PASS`` or ``FAIL``, but + #: library keywords have status ``NOT_RUN`` in the dry-ryn mode. + #: See also :attr:`passed`. + self.status = status + #: Keyword execution start time in format ``%Y%m%d %H:%M:%S.%f``. + self.starttime = starttime + #: Keyword execution end time in format ``%Y%m%d %H:%M:%S.%f``. + self.endtime = endtime + #: Keyword status message. Used only if suite teardowns fails. + self.message = '' + + @property + def elapsedtime(self): + """Total execution time in milliseconds.""" + return utils.get_elapsed_time(self.starttime, self.endtime) + + @property + def name(self): + """Keyword name in format ``libname.kwname``. + + Just ``kwname`` if :attr:`libname` is empty. In practice that is the + case only with user keywords in the same file as the executed test case + or test suite. + + Cannot be set directly. Set :attr:`libname` and :attr:`kwname` + separately instead. + """ + if not self.libname: + return self.kwname + return '%s.%s' % (self.libname, self.kwname) + + @property + def passed(self): + """``True`` or ``False`` depending on the :attr:`status`.""" + return self.status == 'PASS' + + @passed.setter + def passed(self, passed): + self.status = 'PASS' if passed else 'FAIL' + + +class TestCase(model.TestCase): + """Represents results of a single test case. + + See the base class for documentation of attributes not documented here. + """ + __slots__ = ['status', 'message', 'starttime', 'endtime'] + keyword_class = Keyword + + def __init__(self, name='', doc='', tags=None, timeout=None, status='FAIL', + message='', starttime=None, endtime=None): + model.TestCase.__init__(self, name, doc, tags, timeout) + #: Status as a string ``PASS`` or ``FAIL``. See also :attr:`passed`. + self.status = status + #: Test message. Typically a failure message but can be set also when + #: test passes. + self.message = message + #: Test case execution start time in format ``%Y%m%d %H:%M:%S.%f``. + self.starttime = starttime + #: Test case execution end time in format ``%Y%m%d %H:%M:%S.%f``. + self.endtime = endtime + + @property + def elapsedtime(self): + """Total execution time in milliseconds.""" + return utils.get_elapsed_time(self.starttime, self.endtime) + + @property + def passed(self): + """``True/False`` depending on the :attr:`status`.""" + return self.status == 'PASS' + + @passed.setter + def passed(self, passed): + self.status = 'PASS' if passed else 'FAIL' + + @property + def critical(self): + """``True/False`` depending on is the test considered critical. + + Criticality is determined based on test's :attr:`tags` and + :attr:`~TestSuite.criticality` of the :attr:`parent` suite. + """ + if not self.parent: + return True + return self.parent.criticality.test_is_critical(self) + + +class TestSuite(model.TestSuite): + """Represents results of a single test suite. + + See the base class for documentation of attributes not documented here. + """ + __slots__ = ['message', 'starttime', 'endtime', '_criticality'] + test_class = TestCase + keyword_class = Keyword + + def __init__(self, name='', doc='', metadata=None, source=None, + message='', starttime=None, endtime=None, rpa=False): + model.TestSuite.__init__(self, name, doc, metadata, source, rpa) + #: Possible suite setup or teardown error message. + self.message = message + #: Suite execution start time in format ``%Y%m%d %H:%M:%S.%f``. + self.starttime = starttime + #: Suite execution end time in format ``%Y%m%d %H:%M:%S.%f``. + self.endtime = endtime + self._criticality = None + + @property + def passed(self): + """``True`` if no critical test has failed, ``False`` otherwise.""" + return not self.statistics.critical.failed + + @property + def status(self): + """``'PASS'`` if no critical test has failed, ``'FAIL'`` otherwise.""" + return 'PASS' if self.passed else 'FAIL' + + @property + def statistics(self): + """Suite statistics as a :class:`~robot.model.totalstatistics.TotalStatistics` object. + + Recreated every time this property is accessed, so saving the results + to a variable and inspecting it is often a good idea:: + + stats = suite.statistics + print(stats.critical.failed) + print(stats.all.total) + print(stats.message) + """ + return TotalStatisticsBuilder(self, self.rpa).stats + + @property + def full_message(self): + """Combination of :attr:`message` and :attr:`stat_message`.""" + if not self.message: + return self.stat_message + return '%s\n\n%s' % (self.message, self.stat_message) + + @property + def stat_message(self): + """String representation of the :attr:`statistics`.""" + return self.statistics.message + + @property + def elapsedtime(self): + """Total execution time in milliseconds.""" + if self.starttime and self.endtime: + return utils.get_elapsed_time(self.starttime, self.endtime) + return sum(child.elapsedtime for child in + chain(self.suites, self.tests, self.keywords)) + + @property + def criticality(self): + """Used by tests to determine are they considered critical or not. + + Normally configured using ``--critical`` and ``--noncritical`` + command line options. Can be set programmatically using + :meth:`set_criticality` of the root test suite. + """ + if self.parent: + return self.parent.criticality + if self._criticality is None: + self.set_criticality() + return self._criticality + + def set_criticality(self, critical_tags=None, non_critical_tags=None): + """Sets which tags are considered critical and which non-critical. + + :param critical_tags: Tags or patterns considered critical. See + the documentation of the ``--critical`` option for more details. + :param non_critical_tags: Tags or patterns considered non-critical. See + the documentation of the ``--noncritical`` option for more details. + + Tags can be given as lists of strings or, when giving only one, + as single strings. This information is used by tests to determine + are they considered critical or not. + + Criticality can be set only to the root test suite. + """ + if self.parent is not None: + raise ValueError('Criticality can only be set to the root suite.') + self._criticality = Criticality(critical_tags, non_critical_tags) + + def remove_keywords(self, how): + """Remove keywords based on the given condition. + + :param how: What approach to use when removing keywords. Either + ``ALL``, ``PASSED``, ``FOR``, ``WUKS``, or ``NAME:``. + + For more information about the possible values see the documentation + of the ``--removekeywords`` command line option. + """ + self.visit(KeywordRemover(how)) + + def filter_messages(self, log_level='TRACE'): + """Remove log messages below the specified ``log_level``.""" + self.visit(MessageFilter(log_level)) + + def configure(self, **options): + """A shortcut to configure a suite using one method call. + + Can only be used with the root test suite. + + :param options: Passed to + :class:`~robot.result.configurer.SuiteConfigurer` that will then + set suite attributes, call :meth:`filter`, etc. as needed. + + Example:: + + suite.configure(remove_keywords='PASSED', + critical_tags='smoke', + doc='Smoke test results.') + """ + model.TestSuite.configure(self) # Parent validates call is allowed. + self.visit(SuiteConfigurer(**options)) + + def handle_suite_teardown_failures(self): + """Internal usage only.""" + self.visit(SuiteTeardownFailureHandler()) + + def suite_teardown_failed(self, message): + """Internal usage only.""" + self.visit(SuiteTeardownFailed(message)) diff --git a/robot/lib/python3.8/site-packages/robot/result/resultbuilder.py b/robot/lib/python3.8/site-packages/robot/result/resultbuilder.py new file mode 100644 index 0000000000000000000000000000000000000000..65a50ca665c5937fba805b33c7decebbb3389faf --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/resultbuilder.py @@ -0,0 +1,201 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.model import SuiteVisitor +from robot.utils import ET, ETSource, get_error_message, unic + +from .executionresult import Result, CombinedResult +from .flattenkeywordmatcher import (FlattenByNameMatcher, FlattenByTypeMatcher, + FlattenByTagMatcher) +from .merger import Merger +from .xmlelementhandlers import XmlElementHandler + + +def ExecutionResult(*sources, **options): + """Factory method to constructs :class:`~.executionresult.Result` objects. + + :param sources: XML source(s) containing execution results. + Can be specified as paths, opened file objects, or strings/bytes + containing XML directly. Support for bytes is new in RF 3.2. + :param options: Configuration options. + Using ``merge=True`` causes multiple results to be combined so that + tests in the latter results replace the ones in the original. + Setting ``rpa`` either to ``True`` (RPA mode) or ``False`` (test + automation) sets execution mode explicitly. By default it is got + from processed output files and conflicting modes cause an error. + Other options are passed directly to the + :class:`ExecutionResultBuilder` object used internally. + :returns: :class:`~.executionresult.Result` instance. + + Should be imported by external code via the :mod:`robot.api` package. + See the :mod:`robot.result` package for a usage example. + """ + if not sources: + raise DataError('One or more data source needed.') + if options.pop('merge', False): + return _merge_results(sources[0], sources[1:], options) + if len(sources) > 1: + return _combine_results(sources, options) + return _single_result(sources[0], options) + + +def _merge_results(original, merged, options): + result = ExecutionResult(original, **options) + merger = Merger(result, rpa=result.rpa) + for path in merged: + merged = ExecutionResult(path, **options) + merger.merge(merged) + return result + + +def _combine_results(sources, options): + return CombinedResult(ExecutionResult(src, **options) for src in sources) + + +def _single_result(source, options): + ets = ETSource(source) + result = Result(source, rpa=options.pop('rpa', None)) + try: + return ExecutionResultBuilder(ets, **options).build(result) + except IOError as err: + error = err.strerror + except: + error = get_error_message() + raise DataError("Reading XML source '%s' failed: %s" % (unic(ets), error)) + + +class ExecutionResultBuilder(object): + """Builds :class:`~.executionresult.Result` objects based on output files. + + Instead of using this builder directly, it is recommended to use the + :func:`ExecutionResult` factory method. + """ + + def __init__(self, source, include_keywords=True, flattened_keywords=None): + """ + :param source: Path to the XML output file to build + :class:`~.executionresult.Result` objects from. + :param include_keywords: Boolean controlling whether to include + keyword information in the result or not. Keywords are + not needed when generating only report. + :param flatten_keywords: List of patterns controlling what keywords to + flatten. See the documentation of ``--flattenkeywords`` option for + more details. + """ + self._source = source \ + if isinstance(source, ETSource) else ETSource(source) + self._include_keywords = include_keywords + self._flattened_keywords = flattened_keywords + + def build(self, result): + # Parsing is performance optimized. Do not change without profiling! + handler = XmlElementHandler(result) + with self._source as source: + self._parse(source, handler.start, handler.end) + result.handle_suite_teardown_failures() + if not self._include_keywords: + result.suite.visit(RemoveKeywords()) + return result + + def _parse(self, source, start, end): + context = ET.iterparse(source, events=('start', 'end')) + if not self._include_keywords: + context = self._omit_keywords(context) + elif self._flattened_keywords: + context = self._flatten_keywords(context, self._flattened_keywords) + for event, elem in context: + if event == 'start': + start(elem) + else: + end(elem) + elem.clear() + + def _omit_keywords(self, context): + omitted_kws = 0 + for event, elem in context: + # Teardowns aren't omitted to allow checking suite teardown status. + omit = elem.tag == 'kw' and elem.get('type') != 'teardown' + start = event == 'start' + if omit and start: + omitted_kws += 1 + if not omitted_kws: + yield event, elem + elif not start: + elem.clear() + if omit and not start: + omitted_kws -= 1 + + def _flatten_keywords(self, context, flattened): + # Performance optimized. Do not change without profiling! + name_match, by_name = self._get_matcher(FlattenByNameMatcher, flattened) + type_match, by_type = self._get_matcher(FlattenByTypeMatcher, flattened) + tags_match, by_tags = self._get_matcher(FlattenByTagMatcher, flattened) + started = -1 # if 0 or more, we are flattening + tags = [] + inside_kw = 0 # to make sure we don't read tags from a test + seen_doc = False + for event, elem in context: + tag = elem.tag + start = event == 'start' + end = not start + if start and tag == 'kw': + inside_kw += 1 + if started >= 0: + started += 1 + elif by_name and name_match(elem.get('name', ''), elem.get('library')): + started = 0 + seen_doc = False + elif by_type and type_match(elem.get('type', 'kw')): + started = 0 + seen_doc = False + elif started < 0 and by_tags and inside_kw: + if end and tag == 'tag': + tags.append(elem.text or '') + elif end and tag == 'tags': + if tags_match(tags): + started = 0 + seen_doc = False + tags = [] + if end and tag == 'kw': + inside_kw -= 1 + if started == 0 and not seen_doc: + doc = ET.Element('doc') + doc.text = '_*Keyword content flattened.*_' + yield 'start', doc + yield 'end', doc + if started == 0 and end and tag == 'doc': + seen_doc = True + elem.text = ('%s\n\n_*Keyword content flattened.*_' + % (elem.text or '')).strip() + if started <= 0 or tag == 'msg': + yield event, elem + else: + elem.clear() + if started >= 0 and end and tag == 'kw': + started -= 1 + + def _get_matcher(self, matcher_class, flattened): + matcher = matcher_class(flattened) + return matcher.match, bool(matcher) + + +class RemoveKeywords(SuiteVisitor): + + def start_suite(self, suite): + suite.keywords = [] + + def visit_test(self, test): + test.keywords = [] diff --git a/robot/lib/python3.8/site-packages/robot/result/suiteteardownfailed.py b/robot/lib/python3.8/site-packages/robot/result/suiteteardownfailed.py new file mode 100644 index 0000000000000000000000000000000000000000..aab32176de834597ae33d52af85c39ba7530e13f --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/suiteteardownfailed.py @@ -0,0 +1,47 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.model import SuiteVisitor + + +class SuiteTeardownFailureHandler(SuiteVisitor): + + def end_suite(self, suite): + teardown = suite.keywords.teardown + # Both 'PASS' and 'NOT_RUN' (used in dry-run) statuses are OK. + if teardown and teardown.status == 'FAIL': + suite.suite_teardown_failed(teardown.message) + + def visit_test(self, test): + pass + + def visit_keyword(self, keyword): + pass + + +class SuiteTeardownFailed(SuiteVisitor): + _normal_msg = 'Parent suite teardown failed:\n' + _also_msg = '\n\nAlso parent suite teardown failed:\n' + + def __init__(self, error): + self._normal_msg += error + self._also_msg += error + + def visit_test(self, test): + test.status = 'FAIL' + test.message += self._also_msg if test.message else self._normal_msg + + def visit_keyword(self, keyword): + pass diff --git a/robot/lib/python3.8/site-packages/robot/result/visitor.py b/robot/lib/python3.8/site-packages/robot/result/visitor.py new file mode 100644 index 0000000000000000000000000000000000000000..ca0d654c0ded4a01c27d59aaf0a973fc85c842af --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/visitor.py @@ -0,0 +1,124 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Visitors can be used to easily traverse result structures. + +This module contains :class:`ResultVisitor` for traversing the whole +:class:`~robot.result.executionresult.Result` object. It extends +:class:`~robot.model.visitor.SuiteVisitor` that contains visiting logic +for the test suite structure. +""" + +from robot.model import SuiteVisitor + + +class ResultVisitor(SuiteVisitor): + """Abstract class to conveniently travel :class:`~robot.result.executionresult.Result` objects. + + A visitor implementation can be given to the :meth:`visit` method of a + result object. This will cause the result object to be traversed and the + visitor's :meth:`visit_x`, :meth:`start_x`, and :meth:`end_x` methods to + be called for each suite, test, keyword and message, as well as for errors, + statistics, and other information in the result object. See methods below + for a full list of available visitor methods. + + See the :mod:`result package level ` documentation for + more information about handling results and a concrete visitor example. + For more information about the visitor algorithm see documentation in + :mod:`robot.model.visitor` module. + """ + def visit_result(self, result): + if self.start_result(result) is not False: + result.suite.visit(self) + result.statistics.visit(self) + result.errors.visit(self) + self.end_result(result) + + def start_result(self, result): + pass + + def end_result(self, result): + pass + + def visit_statistics(self, stats): + if self.start_statistics(stats) is not False: + stats.total.visit(self) + stats.tags.visit(self) + stats.suite.visit(self) + self.end_statistics(stats) + + def start_statistics(self, stats): + pass + + def end_statistics(self, stats): + pass + + def visit_total_statistics(self, stats): + if self.start_total_statistics(stats) is not False: + for stat in stats: + stat.visit(self) + self.end_total_statistics(stats) + + def start_total_statistics(self, stats): + pass + + def end_total_statistics(self, stats): + pass + + def visit_tag_statistics(self, stats): + if self.start_tag_statistics(stats) is not False: + for stat in stats: + stat.visit(self) + self.end_tag_statistics(stats) + + def start_tag_statistics(self, stats): + pass + + def end_tag_statistics(self, stats): + pass + + def visit_suite_statistics(self, stats): + if self.start_suite_statistics(stats) is not False: + for stat in stats: + stat.visit(self) + self.end_suite_statistics(stats) + + def start_suite_statistics(self, stats): + pass + + def end_suite_statistics(self, suite_stats): + pass + + def visit_stat(self, stat): + if self.start_stat(stat) is not False: + self.end_stat(stat) + + def start_stat(self, stat): + pass + + def end_stat(self, stat): + pass + + def visit_errors(self, errors): + self.start_errors(errors) + for msg in errors: + msg.visit(self) + self.end_errors(errors) + + def start_errors(self, errors): + pass + + def end_errors(self, errors): + pass diff --git a/robot/lib/python3.8/site-packages/robot/result/xmlelementhandlers.py b/robot/lib/python3.8/site-packages/robot/result/xmlelementhandlers.py new file mode 100644 index 0000000000000000000000000000000000000000..382c05ef9869886f6d7f94c2785e06323a439c45 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/result/xmlelementhandlers.py @@ -0,0 +1,262 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError + + +class XmlElementHandler(object): + + def __init__(self, execution_result, root_handler=None): + self._stack = [(root_handler or RootHandler(), execution_result)] + + def start(self, elem): + handler, result = self._stack[-1] + handler = handler.get_child_handler(elem) + result = handler.start(elem, result) + self._stack.append((handler, result)) + + def end(self, elem): + handler, result = self._stack.pop() + handler.end(elem, result) + + +class _Handler(object): + + def __init__(self): + self._child_handlers = dict((c.tag, c) for c in self._children()) + + def _children(self): + return [] + + def get_child_handler(self, elem): + try: + return self._child_handlers[elem.tag] + except KeyError: + raise DataError("Incompatible XML element '%s'." % elem.tag) + + def start(self, elem, result): + return result + + def end(self, elem, result): + pass + + def _timestamp(self, elem, attr_name): + timestamp = elem.get(attr_name) + return timestamp if timestamp != 'N/A' else None + + +class RootHandler(_Handler): + + def _children(self): + return [RobotHandler()] + + +class RobotHandler(_Handler): + tag = 'robot' + + def start(self, elem, result): + generator = elem.get('generator', 'unknown').split()[0].upper() + result.generated_by_robot = generator == 'ROBOT' + if result.rpa is None: + result.rpa = elem.get('rpa', 'false') == 'true' + return result + + def _children(self): + return [RootSuiteHandler(), StatisticsHandler(), ErrorsHandler()] + + +class SuiteHandler(_Handler): + tag = 'suite' + + def start(self, elem, result): + return result.suites.create(name=elem.get('name', ''), + source=elem.get('source'), + rpa=result.rpa) + + def _children(self): + return [DocHandler(), MetadataHandler(), SuiteStatusHandler(), + KeywordHandler(), TestCaseHandler(), self] + + +class RootSuiteHandler(SuiteHandler): + + def start(self, elem, result): + result.suite.name = elem.get('name', '') + result.suite.source = elem.get('source') + result.suite.rpa = result.rpa + return result.suite + + def _children(self): + return SuiteHandler._children(self)[:-1] + [SuiteHandler()] + + +class TestCaseHandler(_Handler): + tag = 'test' + + def start(self, elem, result): + return result.tests.create(name=elem.get('name', '')) + + def _children(self): + return [DocHandler(), TagsHandler(), TimeoutHandler(), + TestStatusHandler(), KeywordHandler()] + + +class KeywordHandler(_Handler): + tag = 'kw' + + def start(self, elem, result): + return result.keywords.create(kwname=elem.get('name', ''), + libname=elem.get('library', ''), + type=elem.get('type', 'kw')) + + def _children(self): + return [DocHandler(), ArgumentsHandler(), AssignHandler(), + TagsHandler(), TimeoutHandler(), KeywordStatusHandler(), + MessageHandler(), self] + + +class MessageHandler(_Handler): + tag = 'msg' + + def end(self, elem, result): + result.messages.create(elem.text or '', + elem.get('level', 'INFO'), + elem.get('html', 'no') == 'yes', + self._timestamp(elem, 'timestamp')) + + +class _StatusHandler(_Handler): + tag = 'status' + + def _set_status(self, elem, result): + result.status = elem.get('status', 'FAIL') + + def _set_message(self, elem, result): + result.message = elem.text or '' + + def _set_times(self, elem, result): + result.starttime = self._timestamp(elem, 'starttime') + result.endtime = self._timestamp(elem, 'endtime') + + +class KeywordStatusHandler(_StatusHandler): + + def end(self, elem, result): + self._set_status(elem, result) + self._set_times(elem, result) + if result.type == result.TEARDOWN_TYPE: + self._set_message(elem, result) + + +class SuiteStatusHandler(_StatusHandler): + + def end(self, elem, result): + self._set_message(elem, result) + self._set_times(elem, result) + + +class TestStatusHandler(_StatusHandler): + + def end(self, elem, result): + self._set_status(elem, result) + self._set_message(elem, result) + self._set_times(elem, result) + + +class DocHandler(_Handler): + tag = 'doc' + + def end(self, elem, result): + result.doc = elem.text or '' + + +class MetadataHandler(_Handler): + tag = 'metadata' + + def _children(self): + return [MetadataItemHandler()] + + +class MetadataItemHandler(_Handler): + tag = 'item' + + def end(self, elem, result): + result.metadata[elem.get('name', '')] = elem.text or '' + + +class TagsHandler(_Handler): + tag = 'tags' + + def _children(self): + return [TagHandler()] + + +class TagHandler(_Handler): + tag = 'tag' + + def end(self, elem, result): + result.tags.add(elem.text or '') + + +class TimeoutHandler(_Handler): + tag = 'timeout' + + def end(self, elem, result): + result.timeout = elem.get('value') + + +class AssignHandler(_Handler): + tag = 'assign' + + def _children(self): + return [AssignVarHandler()] + + +class AssignVarHandler(_Handler): + tag = 'var' + + def end(self, elem, result): + result.assign += (elem.text or '',) + + +class ArgumentsHandler(_Handler): + tag = 'arguments' + + def _children(self): + return [ArgumentHandler()] + + +class ArgumentHandler(_Handler): + tag = 'arg' + + def end(self, elem, result): + result.args += (elem.text or '',) + + +class ErrorsHandler(_Handler): + tag = 'errors' + + def start(self, elem, result): + return result.errors + + def _children(self): + return [MessageHandler()] + + +class StatisticsHandler(_Handler): + tag = 'statistics' + + def get_child_handler(self, elem): + return self diff --git a/robot/lib/python3.8/site-packages/robot/run.py b/robot/lib/python3.8/site-packages/robot/run.py new file mode 100644 index 0000000000000000000000000000000000000000..5b066983579da007bcaade4873a337c0c7a71296 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/run.py @@ -0,0 +1,553 @@ +#!/usr/bin/env python + +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module implementing the command line entry point for executing tests. + +This module can be executed from the command line using the following +approaches:: + + python -m robot.run + python path/to/robot/run.py + +Instead of ``python`` it is possible to use also other Python interpreters. +This module is also used by the installed ``robot`` start-up script. + +This module also provides :func:`run` and :func:`run_cli` functions +that can be used programmatically. Other code is for internal usage. +""" + +import sys + +# Allows running as a script. __name__ check needed with multiprocessing: +# https://github.com/robotframework/robotframework/issues/1137 +if 'robot' not in sys.modules and __name__ == '__main__': + import pythonpathsetter + +from robot.conf import RobotSettings +from robot.model import ModelModifier +from robot.output import LOGGER, pyloggingconf +from robot.reporting import ResultWriter +from robot.running.builder import TestSuiteBuilder +from robot.utils import Application, unic, text + + +USAGE = """Robot Framework -- A generic automation framework + +Version: + +Usage: robot [options] data_sources + or: python -m robot [options] data_sources + or: python path/to/robot [options] data_sources + or: java -jar robotframework.jar [options] data_sources + +Robot Framework is a generic open source automation framework for acceptance +testing, acceptance test-driven development (ATDD) and robotic process +automation (RPA). It has simple, easy-to-use syntax that utilizes the +keyword-driven automation approach. Keywords adding new capabilities are +implemented in libraries using either Python or Java. New higher level +keywords can also be created using Robot Framework's own syntax. + +The easiest way to execute Robot Framework is using the `robot` command created +as part of the normal installation. Alternatively it is possible to execute +the `robot` module directly using `python -m robot`, where `python` can be +replaced with any supported Python interpreter such as `jython`, `ipy` or +`python3`. Yet another alternative is running the `robot` directory like +`python path/to/robot`. Finally, there is a standalone JAR distribution +available. + +Tests (or tasks in RPA terminology) are created in files typically having the +`*.robot` extension. Files automatically create test (or task) suites and +directories with these files create higher level suites. When Robot Framework +is executed, paths to these files or directories are given to it as arguments. + +By default Robot Framework creates an XML output file and a log and a report in +HTML format, but this can be configured using various options listed below. +Outputs in HTML format are for human consumption and XML output for integration +with other systems. XML outputs can also be combined and otherwise further +post-processed with the Rebot tool that is an integral part of Robot Framework. +Run `rebot --help` for more information. + +Robot Framework is open source software released under Apache License 2.0. +For more information about the framework and the rich ecosystem around it +see http://robotframework.org/. + +Options +======= + + --rpa Turn the on generic automation mode. Mainly affects + terminology so that "test" is replaced with "task" + in logs and reports. By default the mode is got + from test/task header in data files. New in RF 3.1. + -F --extension value Parse only files with this extension when executing + a directory. Has no effect when running individual + files or when using resource files. If more than one + extension is needed, separate them with a colon. + Examples: `--extension txt`, `--extension robot:txt` + New in RF 3.0.1. Starting from RF 3.2 only `*.robot` + files are parsed by default. + -N --name name Set the name of the top level suite. By default the + name is created based on the executed file or + directory. + -D --doc documentation Set the documentation of the top level suite. + Simple formatting is supported (e.g. *bold*). If the + documentation contains spaces, it must be quoted. + Example: --doc "Very *good* example" + -M --metadata name:value * Set metadata of the top level suite. Value can + contain formatting similarly as --doc. + Example: --metadata Version:1.2 + -G --settag tag * Sets given tag(s) to all executed tests. + -t --test name * Select tests by name or by long name containing also + parent suite name like `Parent.Test`. Name is case + and space insensitive and it can also be a simple + pattern where `*` matches anything, `?` matches any + single character, and `[chars]` matches one character + in brackets. + --task name * Alias to --test. Especially applicable with --rpa. + -s --suite name * Select suites by name. When this option is used with + --test, --include or --exclude, only tests in + matching suites and also matching other filtering + criteria are selected. Name can be a simple pattern + similarly as with --test and it can contain parent + name separated with a dot. For example, `-s X.Y` + selects suite `Y` only if its parent is `X`. + -i --include tag * Select tests by tag. Similarly as name with --test, + tag is case and space insensitive and it is possible + to use patterns with `*`, `?` and `[]` as wildcards. + Tags and patterns can also be combined together with + `AND`, `OR`, and `NOT` operators. + Examples: --include foo --include bar* + --include fooANDbar* + -e --exclude tag * Select test cases not to run by tag. These tests are + not run even if included with --include. Tags are + matched using same rules as with --include. + -R --rerunfailed output Select failed tests from an earlier output file to be + re-executed. Equivalent to selecting same tests + individually using --test. + -S --rerunfailedsuites output Select failed suites from an earlier output + file to be re-executed. New in RF 3.0.1. + --runemptysuite Executes suite even if it contains no tests. Useful + e.g. with --include/--exclude when it is not an error + that no test matches the condition. + -c --critical tag * Tests having the given tag are considered critical. + If no critical tags are set, all tests are critical. + Tags can be given as a pattern same way as with + --include. + -n --noncritical tag * Tests having the given tag are not critical even if + they have a tag set with --critical. Tag can be + a pattern. + -v --variable name:value * Set variables in the test data. Only scalar + variables with string value are supported and name is + given without `${}`. See --variablefile for a more + powerful variable setting mechanism. + Examples: + --variable str:Hello => ${str} = `Hello` + -v hi:Hi_World -E space:_ => ${hi} = `Hi World` + -v x: -v y:42 => ${x} = ``, ${y} = `42` + -V --variablefile path * Python or YAML file file to read variables from. + Possible arguments to the variable file can be given + after the path using colon or semicolon as separator. + Examples: --variablefile path/vars.yaml + --variablefile environment.py:testing + -d --outputdir dir Where to create output files. The default is the + directory where tests are run from and the given path + is considered relative to that unless it is absolute. + -o --output file XML output file. Given path, similarly as paths given + to --log, --report, --xunit, and --debugfile, is + relative to --outputdir unless given as an absolute + path. Other output files are created based on XML + output files after the test execution and XML outputs + can also be further processed with Rebot tool. Can be + disabled by giving a special value `NONE`. + Default: output.xml + -l --log file HTML log file. Can be disabled by giving a special + value `NONE`. Default: log.html + Examples: `--log mylog.html`, `-l NONE` + -r --report file HTML report file. Can be disabled with `NONE` + similarly as --log. Default: report.html + -x --xunit file xUnit compatible result file. Not created unless this + option is specified. + --xunitskipnoncritical Mark non-critical tests in xUnit output as skipped. + -b --debugfile file Debug file written during execution. Not created + unless this option is specified. + -T --timestampoutputs When this option is used, timestamp in a format + `YYYYMMDD-hhmmss` is added to all generated output + files between their basename and extension. For + example `-T -o output.xml -r report.html -l none` + creates files like `output-20070503-154410.xml` and + `report-20070503-154410.html`. + --splitlog Split the log file into smaller pieces that open in + browsers transparently. + --logtitle title Title for the generated log file. The default title + is ` Test Log`. + --reporttitle title Title for the generated report file. The default + title is ` Test Report`. + --reportbackground colors Background colors to use in the report file. + Either `all_passed:critical_passed:failed` or + `passed:failed`. Both color names and codes work. + Examples: --reportbackground green:yellow:red + --reportbackground #00E:#E00 + --maxerrorlines lines Maximum number of error message lines to show in + report when tests fail. Default is 40, minimum is 10 + and `NONE` can be used to show the full message. + -L --loglevel level Threshold level for logging. Available levels: TRACE, + DEBUG, INFO (default), WARN, NONE (no logging). Use + syntax `LOGLEVEL:DEFAULT` to define the default + visible log level in log files. + Examples: --loglevel DEBUG + --loglevel DEBUG:INFO + --suitestatlevel level How many levels to show in `Statistics by Suite` + in log and report. By default all suite levels are + shown. Example: --suitestatlevel 3 + --tagstatinclude tag * Include only matching tags in `Statistics by Tag` + in log and report. By default all tags are shown. + Given tag can be a pattern like with --include. + --tagstatexclude tag * Exclude matching tags from `Statistics by Tag`. + This option can be used with --tagstatinclude + similarly as --exclude is used with --include. + --tagstatcombine tags:name * Create combined statistics based on tags. + These statistics are added into `Statistics by Tag`. + If the optional `name` is not given, name of the + combined tag is got from the specified tags. Tags are + matched using the same rules as with --include. + Examples: --tagstatcombine requirement-* + --tagstatcombine tag1ANDtag2:My_name + --tagdoc pattern:doc * Add documentation to tags matching the given + pattern. Documentation is shown in `Test Details` and + also as a tooltip in `Statistics by Tag`. Pattern can + use `*`, `?` and `[]` as wildcards like --test. + Documentation can contain formatting like --doc. + Examples: --tagdoc mytag:Example + --tagdoc "owner-*:Original author" + --tagstatlink pattern:link:title * Add external links into `Statistics by + Tag`. Pattern can use `*`, `?` and `[]` as wildcards + like --test. Characters matching to `*` and `?` + wildcards can be used in link and title with syntax + %N, where N is index of the match (starting from 1). + Examples: --tagstatlink mytag:http://my.domain:Title + --tagstatlink "bug-*:http://url/id=%1:Issue Tracker" + --expandkeywords name:|tag: * + Matching keywords will be automatically expanded in + the log file. Matching against keyword name or tags + work using same rules as with --removekeywords. + Examples: --expandkeywords name:BuiltIn.Log + --expandkeywords tag:expand + New in RF 3.2. + --removekeywords all|passed|for|wuks|name:|tag: * + Remove keyword data from the generated log file. + Keywords containing warnings are not removed except + in the `all` mode. + all: remove data from all keywords + passed: remove data only from keywords in passed + test cases and suites + for: remove passed iterations from for loops + wuks: remove all but the last failing keyword + inside `BuiltIn.Wait Until Keyword Succeeds` + name:: remove data from keywords that match + the given pattern. The pattern is matched + against the full name of the keyword (e.g. + 'MyLib.Keyword', 'resource.Second Keyword'), + is case, space, and underscore insensitive, + and may contain `*`, `?` and `[]` wildcards. + Examples: --removekeywords name:Lib.HugeKw + --removekeywords name:myresource.* + tag:: remove data from keywords that match + the given pattern. Tags are case and space + insensitive and patterns can contain `*`, + `?` and `[]` wildcards. Tags and patterns + can also be combined together with `AND`, + `OR`, and `NOT` operators. + Examples: --removekeywords foo + --removekeywords fooANDbar* + --flattenkeywords for|foritem|name:|tag: * + Flattens matching keywords in the generated log file. + Matching keywords get all log messages from their + child keywords and children are discarded otherwise. + for: flatten for loops fully + foritem: flatten individual for loop iterations + name:: flatten matched keywords using same + matching rules as with + `--removekeywords name:` + tag:: flatten matched keywords using same + matching rules as with + `--removekeywords tag:` + --listener class * A class for monitoring test execution. Gets + notifications e.g. when tests start and end. + Arguments to the listener class can be given after + the name using a colon or a semicolon as a separator. + Examples: --listener MyListenerClass + --listener path/to/Listener.py:arg1:arg2 + --nostatusrc Sets the return code to zero regardless of failures + in test cases. Error codes are returned normally. + --dryrun Verifies test data and runs tests so that library + keywords are not executed. + -X --exitonfailure Stops test execution if any critical test fails. + Short option -X is new in RF 3.0.1. + --exitonerror Stops test execution if any error occurs when parsing + test data, importing libraries, and so on. + --skipteardownonexit Causes teardowns to be skipped if test execution is + stopped prematurely. + --randomize all|suites|tests|none Randomizes the test execution order. + all: randomizes both suites and tests + suites: randomizes suites + tests: randomizes tests + none: no randomization (default) + Use syntax `VALUE:SEED` to give a custom random seed. + The seed must be an integer. + Examples: --randomize all + --randomize tests:1234 + --prerunmodifier class * Class to programmatically modify the test suite + structure before execution. + --prerebotmodifier class * Class to programmatically modify the result + model before creating reports and logs. + --console type How to report execution on the console. + verbose: report every suite and test (default) + dotted: only show `.` for passed test, `f` for + failed non-critical tests, and `F` for + failed critical tests + quiet: no output except for errors and warnings + none: no output whatsoever + -. --dotted Shortcut for `--console dotted`. + --quiet Shortcut for `--console quiet`. + -W --consolewidth chars Width of the console output. Default is 78. + -C --consolecolors auto|on|ansi|off Use colors on console output or not. + auto: use colors when output not redirected (default) + on: always use colors + ansi: like `on` but use ANSI colors also on Windows + off: disable colors altogether + Note that colors do not work with Jython on Windows. + -K --consolemarkers auto|on|off Show markers on the console when top level + keywords in a test case end. Values have same + semantics as with --consolecolors. + -P --pythonpath path * Additional locations (directories, ZIPs, JARs) where + to search test libraries and other extensions when + they are imported. Multiple paths can be given by + separating them with a colon (`:`) or by using this + option several times. Given path can also be a glob + pattern matching multiple paths. + Examples: + --pythonpath libs/ --pythonpath resources/*.jar + --pythonpath /opt/testlibs:mylibs.zip:yourlibs + -A --argumentfile path * Text file to read more arguments from. Use special + path `STDIN` to read contents from the standard input + stream. File can have both options and input files + or directories, one per line. Contents do not need to + be escaped but spaces in the beginning and end of + lines are removed. Empty lines and lines starting + with a hash character (#) are ignored. + Example file: + | --include regression + | --name Regression Tests + | # This is a comment line + | my_tests.robot + | path/to/test/directory/ + Examples: + --argumentfile argfile.txt --argumentfile STDIN + -h -? --help Print usage instructions. + --version Print version information. + +Options that are marked with an asterisk (*) can be specified multiple times. +For example, `--test first --test third` selects test cases with name `first` +and `third`. If an option accepts a value but is not marked with an asterisk, +the last given value has precedence. For example, `--log A.html --log B.html` +creates log file `B.html`. Options accepting no values can be disabled by +using the same option again with `no` prefix added or dropped. The last option +has precedence regardless of how many times options are used. For example, +`--dryrun --dryrun --nodryrun --nostatusrc --statusrc` would not activate the +dry-run mode and would return a normal return code. + +Long option format is case-insensitive. For example, --SuiteStatLevel is +equivalent to but easier to read than --suitestatlevel. Long options can +also be shortened as long as they are unique. For example, `--logti Title` +works while `--lo log.html` does not because the former matches only --logtitle +but the latter matches --log, --loglevel and --logtitle. + +Environment Variables +===================== + +ROBOT_OPTIONS Space separated list of default options to be placed + in front of any explicit options on the command line. +ROBOT_SYSLOG_FILE Path to a file where Robot Framework writes internal + information about parsing test case files and running + tests. Can be useful when debugging problems. If not + set, or set to a special value `NONE`, writing to the + syslog file is disabled. +ROBOT_SYSLOG_LEVEL Log level to use when writing to the syslog file. + Available levels are the same as with --loglevel + command line option and the default is INFO. +ROBOT_INTERNAL_TRACES When set to any non-empty value, Robot Framework's + internal methods are included in error tracebacks. + +Examples +======== + +# Simple test run using `robot` command without options. +$ robot tests.robot + +# Using options. +$ robot --include smoke --name "Smoke Tests" path/to/tests.robot + +# Executing `robot` module using Python. +$ python -m robot path/to/tests + +# Running `robot` directory with Jython. +$ jython /opt/robot tests.robot + +# Executing multiple test case files and using case-insensitive long options. +$ robot --SuiteStatLevel 2 --Metadata Version:3 tests/*.robot more/tests.robot + +# Setting default options and syslog file before running tests. +$ export ROBOT_OPTIONS="--critical regression --suitestatlevel 2" +$ export ROBOT_SYSLOG_FILE=/tmp/syslog.txt +$ robot tests.robot +""" + + +class RobotFramework(Application): + + def __init__(self): + Application.__init__(self, USAGE, arg_limits=(1,), + env_options='ROBOT_OPTIONS', logger=LOGGER) + + def main(self, datasources, **options): + settings = RobotSettings(options) + LOGGER.register_console_logger(**settings.console_output_config) + LOGGER.info('Settings:\n%s' % unic(settings)) + builder = TestSuiteBuilder(settings['SuiteNames'], + included_extensions=settings.extension, + rpa=settings.rpa, + allow_empty_suite=settings.run_empty_suite) + suite = builder.build(*datasources) + settings.rpa = suite.rpa + if settings.pre_run_modifiers: + suite.visit(ModelModifier(settings.pre_run_modifiers, + settings.run_empty_suite, LOGGER)) + suite.configure(**settings.suite_config) + with pyloggingconf.robot_handler_enabled(settings.log_level): + old_max_error_lines = text.MAX_ERROR_LINES + text.MAX_ERROR_LINES = settings.max_error_lines + try: + result = suite.run(settings) + finally: + text.MAX_ERROR_LINES = old_max_error_lines + LOGGER.info("Tests execution ended. Statistics:\n%s" + % result.suite.stat_message) + if settings.log or settings.report or settings.xunit: + writer = ResultWriter(settings.output if settings.log + else result) + writer.write_results(settings.get_rebot_settings()) + return result.return_code + + def validate(self, options, arguments): + return self._filter_options_without_value(options), arguments + + def _filter_options_without_value(self, options): + return dict((name, value) for name, value in options.items() + if value not in (None, [])) + + +def run_cli(arguments=None, exit=True): + """Command line execution entry point for running tests. + + :param arguments: Command line options and arguments as a list of strings. + Starting from RF 3.1, defaults to ``sys.argv[1:]`` if not given. + :param exit: If ``True``, call ``sys.exit`` with the return code denoting + execution status, otherwise just return the rc. New in RF 3.0.1. + + Entry point used when running tests from the command line, but can also + be used by custom scripts that execute tests. Especially useful if the + script itself needs to accept same arguments as accepted by Robot Framework, + because the script can just pass them forward directly along with the + possible default values it sets itself. + + Example:: + + from robot import run_cli + + # Run tests and return the return code. + rc = run_cli(['--name', 'Example', 'tests.robot'], exit=False) + + # Run tests and exit to the system automatically. + run_cli(['--name', 'Example', 'tests.robot']) + + See also the :func:`run` function that allows setting options as keyword + arguments like ``name="Example"`` and generally has a richer API for + programmatic test execution. + """ + if arguments is None: + arguments = sys.argv[1:] + return RobotFramework().execute_cli(arguments, exit=exit) + + +def run(*tests, **options): + """Programmatic entry point for running tests. + + :param tests: Paths to test case files/directories to be executed similarly + as when running the ``robot`` command on the command line. + :param options: Options to configure and control execution. Accepted + options are mostly same as normal command line options to the ``robot`` + command. Option names match command line option long names without + hyphens so that, for example, ``--name`` becomes ``name``. + + Most options that can be given from the command line work. An exception + is that options ``--pythonpath``, ``--argumentfile``, ``--help`` and + ``--version`` are not supported. + + Options that can be given on the command line multiple times can be + passed as lists. For example, ``include=['tag1', 'tag2']`` is equivalent + to ``--include tag1 --include tag2``. If such options are used only once, + they can be given also as a single string like ``include='tag'``. + + Options that accept no value can be given as Booleans. For example, + ``dryrun=True`` is same as using the ``--dryrun`` option. + + Options that accept string ``NONE`` as a special value can also be used + with Python ``None``. For example, using ``log=None`` is equivalent to + ``--log NONE``. + + ``listener``, ``prerunmodifier`` and ``prerebotmodifier`` options allow + passing values as Python objects in addition to module names these command + line options support. For example, ``run('tests', listener=MyListener())``. + + To capture the standard output and error streams, pass an open file or + file-like object as special keyword arguments ``stdout`` and ``stderr``, + respectively. + + A return code is returned similarly as when running on the command line. + Zero means that tests were executed and no critical test failed, values up + to 250 denote the number of failed critical tests, and values between + 251-255 are for other statuses documented in the Robot Framework User Guide. + + Example:: + + from robot import run + + run('path/to/tests.robot') + run('tests.robot', include=['tag1', 'tag2'], splitlog=True) + with open('stdout.txt', 'w') as stdout: + run('t1.robot', 't2.robot', name='Example', log=None, stdout=stdout) + + Equivalent command line usage:: + + robot path/to/tests.robot + robot --include tag1 --include tag2 --splitlog tests.robot + robot --name Example --log NONE t1.robot t2.robot > stdout.txt + """ + return RobotFramework().execute(*tests, **options) + + +if __name__ == '__main__': + run_cli(sys.argv[1:]) diff --git a/robot/lib/python3.8/site-packages/robot/running/__init__.py b/robot/lib/python3.8/site-packages/robot/running/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c600fc1c02eee18a8b0888fc964ef15c87007bbb --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/__init__.py @@ -0,0 +1,104 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Implements the core test execution logic. + +The main public entry points of this package are of the following two classes: + +* :class:`~robot.running.builder.builders.TestSuiteBuilder` for creating + executable test suites based on existing test case files and directories. + +* :class:`~robot.running.model.TestSuite` for creating an executable + test suite structure programmatically. + +It is recommended to import both of these classes via the :mod:`robot.api` +package like in the examples below. Also :class:`~robot.running.model.TestCase` +and :class:`~robot.running.model.Keyword` classes used internally by the +:class:`~robot.running.model.TestSuite` class are part of the public API. +In those rare cases where these classes are needed directly, they can be +imported from this package. + +Examples +-------- + +First, let's assume we have the following test suite in file +``activate_skynet.robot``:: + + *** Settings *** + Library OperatingSystem + + *** Test Cases *** + Should Activate Skynet + [Tags] smoke + [Setup] Set Environment Variable SKYNET activated + Environment Variable Should Be Set SKYNET + +We can easily parse and create an executable test suite based on the above file +using the :class:`~robot.running.builder.TestSuiteBuilder` class as follows:: + + from robot.api import TestSuiteBuilder + + suite = TestSuiteBuilder().build('path/to/activate_skynet.robot') + +That was easy. Let's next generate the same test suite from scratch +using the :class:`~robot.running.model.TestSuite` class:: + + from robot.api import TestSuite + + suite = TestSuite('Activate Skynet') + suite.resource.imports.library('OperatingSystem') + test = suite.tests.create('Should Activate Skynet', tags=['smoke']) + test.keywords.create('Set Environment Variable', args=['SKYNET', 'activated'], type='setup') + test.keywords.create('Environment Variable Should Be Set', args=['SKYNET']) + +Not that complicated either, especially considering the flexibility. Notice +that the suite created based on the file could also be edited further using +the same API. + +Now that we have a test suite ready, let's :meth:`execute it +` and verify that the returned +:class:`~robot.result.executionresult.Result` object contains correct +information:: + + result = suite.run(critical='smoke', output='skynet.xml') + + assert result.return_code == 0 + assert result.suite.name == 'Activate Skynet' + test = result.suite.tests[0] + assert test.name == 'Should Activate Skynet' + assert test.passed and test.critical + stats = result.suite.statistics + assert stats.critical.total == 1 and stats.critical.failed == 0 + +Running the suite generates a normal output XML file, unless it is disabled +by using ``output=None``. Generating log, report, and xUnit files based on +the results is possible using the +:class:`~robot.reporting.resultwriter.ResultWriter` class:: + + from robot.api import ResultWriter + + # Report and xUnit files can be generated based on the result object. + ResultWriter(result).write_results(report='skynet.html', log=None) + # Generating log files requires processing the earlier generated output XML. + ResultWriter('skynet.xml').write_results() +""" + +from .builder import TestSuiteBuilder, ResourceFileBuilder +from .context import EXECUTION_CONTEXTS +from .model import Keyword, TestCase, TestSuite +from .testlibraries import TestLibrary +from .usererrorhandler import UserErrorHandler +from .userkeyword import UserLibrary +from .runkwregister import RUN_KW_REGISTER diff --git a/robot/lib/python3.8/site-packages/robot/running/arguments/__init__.py b/robot/lib/python3.8/site-packages/robot/running/arguments/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..57c5cdecae52c8d397c9e7e246b9f528d0fc3e29 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/arguments/__init__.py @@ -0,0 +1,26 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import JYTHON + +from .argumentmapper import DefaultValue +from .argumentparser import (PythonArgumentParser, UserKeywordArgumentParser, + DynamicArgumentParser, JavaArgumentParser) +from .argumentspec import ArgumentSpec +from .embedded import EmbeddedArguments +if JYTHON: + from .javaargumentcoercer import JavaArgumentCoercer +else: + JavaArgumentCoercer = None diff --git a/robot/lib/python3.8/site-packages/robot/running/arguments/argumentconverter.py b/robot/lib/python3.8/site-packages/robot/running/arguments/argumentconverter.py new file mode 100644 index 0000000000000000000000000000000000000000..5c75756ca2c06f976b01f33c7556827af5436118 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/arguments/argumentconverter.py @@ -0,0 +1,64 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import is_unicode +from robot.variables import contains_variable + +from .typeconverters import TypeConverter + + +class ArgumentConverter(object): + + def __init__(self, argspec, dry_run=False): + """:type argspec: :py:class:`robot.running.arguments.ArgumentSpec`""" + self._argspec = argspec + self._dry_run = dry_run + + def convert(self, positional, named): + return self._convert_positional(positional), self._convert_named(named) + + def _convert_positional(self, positional): + names = self._argspec.positional + converted = [self._convert(name, value) + for name, value in zip(names, positional)] + if self._argspec.varargs: + converted.extend(self._convert(self._argspec.varargs, value) + for value in positional[len(names):]) + return converted + + def _convert_named(self, named): + names = set(self._argspec.positional) | set(self._argspec.kwonlyargs) + kwargs = self._argspec.kwargs + return [(name, self._convert(name if name in names else kwargs, value)) + for name, value in named] + + def _convert(self, name, value): + type_, explicit_type = self._get_type(name, value) + if type_ is None or (self._dry_run and + contains_variable(value, identifiers='$@&%')): + return value + converter = TypeConverter.converter_for(type_) + if converter is None: + return value + return converter.convert(name, value, explicit_type) + + def _get_type(self, name, value): + if self._argspec.types is None or not is_unicode(value): + return None, None + if name in self._argspec.types: + return self._argspec.types[name], True + if name in self._argspec.defaults: + return type(self._argspec.defaults[name]), False + return None, None diff --git a/robot/lib/python3.8/site-packages/robot/running/arguments/argumentmapper.py b/robot/lib/python3.8/site-packages/robot/running/arguments/argumentmapper.py new file mode 100644 index 0000000000000000000000000000000000000000..0c6357915ded91c194e25a63d8bee0e06bfc3cdf --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/arguments/argumentmapper.py @@ -0,0 +1,81 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError + + +class ArgumentMapper(object): + + def __init__(self, argspec): + """:type argspec: :py:class:`robot.running.arguments.ArgumentSpec`""" + self._argspec = argspec + + def map(self, positional, named, replace_defaults=True): + template = KeywordCallTemplate(self._argspec) + template.fill_positional(positional) + template.fill_named(named) + if replace_defaults: + template.replace_defaults() + return template.args, template.kwargs + + +class KeywordCallTemplate(object): + + def __init__(self, argspec): + """:type argspec: :py:class:`robot.running.arguments.ArgumentSpec`""" + self._argspec = argspec + self.args = [None if arg not in argspec.defaults + else DefaultValue(argspec.defaults[arg]) + for arg in argspec.positional] + self.kwargs = [] + + def fill_positional(self, positional): + self.args[:len(positional)] = positional + + def fill_named(self, named): + spec = self._argspec + for name, value in named: + if name in spec.positional and spec.supports_named: + index = spec.positional.index(name) + self.args[index] = value + elif spec.kwargs or name in spec.kwonlyargs: + self.kwargs.append((name, value)) + else: + raise DataError("Non-existing named argument '%s'." % name) + named_names = {name for name, _ in named} + for name in spec.kwonlyargs: + if name not in named_names: + value = DefaultValue(spec.defaults[name]) + self.kwargs.append((name, value)) + + def replace_defaults(self): + is_default = lambda arg: isinstance(arg, DefaultValue) + while self.args and is_default(self.args[-1]): + self.args.pop() + self.args = [a if not is_default(a) else a.value for a in self.args] + self.kwargs = [(n, v) for n, v in self.kwargs if not is_default(v)] + + +class DefaultValue(object): + + def __init__(self, value): + self.value = value + + def resolve(self, variables): + try: + return variables.replace_scalar(self.value) + except DataError as err: + raise DataError('Resolving argument default values failed: %s' + % err.message) diff --git a/robot/lib/python3.8/site-packages/robot/running/arguments/argumentparser.py b/robot/lib/python3.8/site-packages/robot/running/arguments/argumentparser.py new file mode 100644 index 0000000000000000000000000000000000000000..401324360b7b6e9e5d70b0193de88b4a4f2e9d40 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/arguments/argumentparser.py @@ -0,0 +1,314 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.utils import (JYTHON, PY_VERSION, PY2, is_string, split_from_equals, + unwrap) +from robot.variables import is_assign + +from .argumentspec import ArgumentSpec + +if PY2: + from inspect import getargspec, ismethod + + def getfullargspec(func): + return getargspec(func) + ([], None, {}) +else: + from inspect import getfullargspec, ismethod + +if PY_VERSION >= (3, 5): + import typing +else: + typing = None + +if JYTHON: + from java.lang import Class + from java.util import List, Map + + +class _ArgumentParser(object): + + def __init__(self, type='Keyword'): + self._type = type + + def parse(self, source, name=None): + raise NotImplementedError + + +class PythonArgumentParser(_ArgumentParser): + + def parse(self, handler, name=None): + args, varargs, kws, defaults, kwo, kwo_defaults, annotations \ + = self._get_arg_spec(handler) + if ismethod(handler) or handler.__name__ == '__init__': + args = args[1:] # Drop 'self'. + spec = ArgumentSpec( + name, + self._type, + positional=args, + varargs=varargs, + kwargs=kws, + kwonlyargs=kwo, + defaults=self._get_defaults(args, defaults, kwo_defaults) + ) + spec.types = self._get_types(handler, annotations, spec) + return spec + + def _get_arg_spec(self, handler): + handler = unwrap(handler) + try: + return getfullargspec(handler) + except TypeError: # Can occur w/ C functions (incl. many builtins). + return [], 'args', None, None, [], None, {} + + def _get_defaults(self, args, default_values, kwo_defaults): + if default_values: + defaults = dict(zip(args[-len(default_values):], default_values)) + else: + defaults = {} + if kwo_defaults: + defaults.update(kwo_defaults) + return defaults + + def _get_types(self, handler, annotations, spec): + types = getattr(handler, 'robot_types', ()) + if types is None: + return None + if types: + return types + return self._get_type_hints(handler, annotations, spec) + + def _get_type_hints(self, handler, annotations, spec): + if not typing: + return annotations + try: + type_hints = typing.get_type_hints(handler) + except Exception: # Can raise pretty much anything + return annotations + self._remove_mismatching_type_hints(type_hints, spec.argument_names) + self._remove_optional_none_type_hints(type_hints, spec.defaults) + return type_hints + + def _remove_mismatching_type_hints(self, type_hints, argument_names): + # typing.get_type_hints returns info from the original function even + # if it is decorated. Argument names are got from the wrapping + # decorator and thus there is a mismatch that needs to be resolved. + mismatch = set(type_hints) - set(argument_names) + for name in mismatch: + type_hints.pop(name) + + def _remove_optional_none_type_hints(self, type_hints, defaults): + # If argument has None as a default, typing.get_type_hints adds + # optional None to the information it returns. We don't want that. + for arg in defaults: + if defaults[arg] is None and arg in type_hints: + type_ = type_hints[arg] + if self._is_union(type_): + try: + types = type_.__args__ + except AttributeError: + # Python 3.5.2's typing uses __union_params__ instead + # of __args__. This block can likely be safely removed + # when Python 3.5 support is dropped + types = type_.__union_params__ + if len(types) == 2 and types[1] is type(None): + type_hints[arg] = types[0] + + def _is_union(self, type_): + if PY_VERSION >= (3, 7) and hasattr(type_, '__origin__'): + type_ = type_.__origin__ + return isinstance(type_, type(typing.Union)) + + +class JavaArgumentParser(_ArgumentParser): + + def parse(self, signatures, name=None): + if not signatures: + return self._no_signatures_arg_spec(name) + elif len(signatures) == 1: + return self._single_signature_arg_spec(signatures[0], name) + else: + return self._multi_signature_arg_spec(signatures, name) + + def _no_signatures_arg_spec(self, name): + # Happens when a class has no public constructors + return self._format_arg_spec(name) + + def _single_signature_arg_spec(self, signature, name): + varargs, kwargs = self._get_varargs_and_kwargs_support(signature.args) + positional = len(signature.args) - int(varargs) - int(kwargs) + return self._format_arg_spec(name, positional, varargs=varargs, + kwargs=kwargs) + + def _get_varargs_and_kwargs_support(self, args): + if not args: + return False, False + if self._is_varargs_type(args[-1]): + return True, False + if not self._is_kwargs_type(args[-1]): + return False, False + if len(args) > 1 and self._is_varargs_type(args[-2]): + return True, True + return False, True + + def _is_varargs_type(self, arg): + return arg is List or isinstance(arg, Class) and arg.isArray() + + def _is_kwargs_type(self, arg): + return arg is Map + + def _multi_signature_arg_spec(self, signatures, name): + mina = maxa = len(signatures[0].args) + for sig in signatures[1:]: + argc = len(sig.args) + mina = min(argc, mina) + maxa = max(argc, maxa) + return self._format_arg_spec(name, maxa, maxa-mina) + + def _format_arg_spec(self, name, positional=0, defaults=0, varargs=False, + kwargs=False): + positional = ['arg%d' % (i+1) for i in range(positional)] + if defaults: + defaults = {name: '' for name in positional[-defaults:]} + else: + defaults = {} + return ArgumentSpec(name, self._type, + positional=positional, + varargs='varargs' if varargs else None, + kwargs='kwargs' if kwargs else None, + defaults=defaults, + supports_named=False) + + +class _ArgumentSpecParser(_ArgumentParser): + + def parse(self, argspec, name=None): + spec = ArgumentSpec(name, self._type) + kw_only_args = False + for arg in argspec: + arg = self._validate_arg(arg) + if spec.kwargs: + self._raise_invalid_spec('Only last argument can be kwargs.') + elif isinstance(arg, tuple): + arg, default = arg + arg = self._add_arg(spec, arg, kw_only_args) + spec.defaults[arg] = default + elif self._is_kwargs(arg): + spec.kwargs = self._format_kwargs(arg) + elif self._is_varargs(arg): + if kw_only_args: + self._raise_invalid_spec('Cannot have multiple varargs.') + if not self._is_kw_only_separator(arg): + spec.varargs = self._format_varargs(arg) + kw_only_args = True + elif spec.defaults and not kw_only_args: + self._raise_invalid_spec('Non-default argument after default ' + 'arguments.') + else: + self._add_arg(spec, arg, kw_only_args) + return spec + + def _validate_arg(self, arg): + raise NotImplementedError + + def _raise_invalid_spec(self, error): + raise DataError('Invalid argument specification: %s' % error) + + def _is_kwargs(self, arg): + raise NotImplementedError + + def _format_kwargs(self, kwargs): + raise NotImplementedError + + def _is_kw_only_separator(self, arg): + raise NotImplementedError + + def _is_varargs(self, arg): + raise NotImplementedError + + def _format_varargs(self, varargs): + raise NotImplementedError + + def _format_arg(self, arg): + return arg + + def _add_arg(self, spec, arg, kw_only_arg=False): + arg = self._format_arg(arg) + target = spec.positional if not kw_only_arg else spec.kwonlyargs + target.append(arg) + return arg + + +class DynamicArgumentParser(_ArgumentSpecParser): + + def _validate_arg(self, arg): + if isinstance(arg, tuple): + if self._is_invalid_tuple(arg): + self._raise_invalid_spec('Invalid argument "%s".' % (arg,)) + if len(arg) == 1: + return arg[0] + return arg + if '=' in arg: + return tuple(arg.split('=', 1)) + return arg + + def _is_invalid_tuple(self, arg): + return (len(arg) > 2 + or not is_string(arg[0]) + or (arg[0].startswith('*') and len(arg) > 1)) + + def _is_kwargs(self, arg): + return arg.startswith('**') + + def _format_kwargs(self, kwargs): + return kwargs[2:] + + def _is_varargs(self, arg): + return arg.startswith('*') + + def _is_kw_only_separator(self, arg): + return arg == '*' + + def _format_varargs(self, varargs): + return varargs[1:] + + +class UserKeywordArgumentParser(_ArgumentSpecParser): + + def _validate_arg(self, arg): + arg, default = split_from_equals(arg) + if not (is_assign(arg) or arg == '@{}'): + self._raise_invalid_spec("Invalid argument syntax '%s'." % arg) + if default is not None: + return arg, default + return arg + + def _is_kwargs(self, arg): + return arg[0] == '&' + + def _format_kwargs(self, kwargs): + return kwargs[2:-1] + + def _is_varargs(self, arg): + return arg[0] == '@' + + def _is_kw_only_separator(self, arg): + return arg == '@{}' + + def _format_varargs(self, varargs): + return varargs[2:-1] + + def _format_arg(self, arg): + return arg[2:-1] diff --git a/robot/lib/python3.8/site-packages/robot/running/arguments/argumentresolver.py b/robot/lib/python3.8/site-packages/robot/running/arguments/argumentresolver.py new file mode 100644 index 0000000000000000000000000000000000000000..c04132ed6b6b893e1b0e0a09389d58f5e3f67ca9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/arguments/argumentresolver.py @@ -0,0 +1,130 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.utils import is_string, is_dict_like, split_from_equals +from robot.variables import is_dict_variable + +from .argumentvalidator import ArgumentValidator + + +class ArgumentResolver(object): + + def __init__(self, argspec, resolve_named=True, + resolve_variables_until=None, dict_to_kwargs=False): + self._named_resolver = NamedArgumentResolver(argspec) \ + if resolve_named else NullNamedArgumentResolver() + self._variable_replacer = VariableReplacer(resolve_variables_until) + self._dict_to_kwargs = DictToKwargs(argspec, dict_to_kwargs) + self._argument_validator = ArgumentValidator(argspec) + + def resolve(self, arguments, variables=None): + positional, named = self._named_resolver.resolve(arguments, variables) + positional, named = self._variable_replacer.replace(positional, named, + variables) + positional, named = self._dict_to_kwargs.handle(positional, named) + self._argument_validator.validate(positional, named, + dryrun=variables is None) + return positional, named + + +class NamedArgumentResolver(object): + + def __init__(self, argspec): + self._argspec = argspec + + def resolve(self, arguments, variables=None): + positional = [] + named = [] + for arg in arguments: + if is_dict_variable(arg): + named.append(arg) + elif self._is_named(arg, named, variables): + named.append(split_from_equals(arg)) + elif named: + self._raise_positional_after_named() + else: + positional.append(arg) + return positional, named + + def _is_named(self, arg, previous_named, variables=None): + name, value = split_from_equals(arg) + if value is None: + return False + if variables: + try: + name = variables.replace_scalar(name) + except DataError: + return False + argspec = self._argspec + if previous_named or name in argspec.kwonlyargs or argspec.kwargs: + return True + return argspec.supports_named and name in argspec.positional + + def _raise_positional_after_named(self): + raise DataError("%s '%s' got positional argument after named arguments." + % (self._argspec.type, self._argspec.name)) + + +class NullNamedArgumentResolver(object): + + def resolve(self, arguments, variables=None): + return arguments, {} + + +class DictToKwargs(object): + + def __init__(self, argspec, enabled=False): + self._maxargs = argspec.maxargs + self._enabled = enabled and bool(argspec.kwargs) + + def handle(self, positional, named): + if self._enabled and self._extra_arg_has_kwargs(positional, named): + named = positional.pop().items() + return positional, named + + def _extra_arg_has_kwargs(self, positional, named): + if named or len(positional) != self._maxargs + 1: + return False + return is_dict_like(positional[-1]) + + +class VariableReplacer(object): + + def __init__(self, resolve_until=None): + self._resolve_until = resolve_until + + def replace(self, positional, named, variables=None): + # `variables` is None in dry-run mode and when using Libdoc. + if variables: + positional = variables.replace_list(positional, self._resolve_until) + named = list(self._replace_named(named, variables.replace_scalar)) + else: + positional = list(positional) + named = [item for item in named if isinstance(item, tuple)] + return positional, named + + def _replace_named(self, named, replace_scalar): + for item in named: + for name, value in self._get_replaced_named(item, replace_scalar): + if not is_string(name): + raise DataError('Argument names must be strings.') + yield name, value + + def _get_replaced_named(self, item, replace_scalar): + if not isinstance(item, tuple): + return replace_scalar(item).items() + name, value = item + return [(replace_scalar(name), replace_scalar(value))] diff --git a/robot/lib/python3.8/site-packages/robot/running/arguments/argumentspec.py b/robot/lib/python3.8/site-packages/robot/running/arguments/argumentspec.py new file mode 100644 index 0000000000000000000000000000000000000000..49bd99135e1f515fbe3e4e841b58bec965639018 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/arguments/argumentspec.py @@ -0,0 +1,71 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from robot.utils import setter + +from .argumentconverter import ArgumentConverter +from .argumentmapper import ArgumentMapper +from .argumentresolver import ArgumentResolver +from .typevalidator import TypeValidator + + +class ArgumentSpec(object): + + def __init__(self, name=None, type='Keyword', positional=None, + varargs=None, kwonlyargs=None, kwargs=None, defaults=None, + types=None, supports_named=True): + self.name = name + self.type = type + self.positional = positional or [] + self.varargs = varargs + self.kwonlyargs = kwonlyargs or [] + self.kwargs = kwargs + self.defaults = defaults or {} + self.types = types + self.supports_named = supports_named + + @setter + def types(self, types): + return TypeValidator(self).validate(types) + + @property + def minargs(self): + required = [arg for arg in self.positional if arg not in self.defaults] + return len(required) + + @property + def maxargs(self): + return len(self.positional) if not self.varargs else sys.maxsize + + @property + def argument_names(self): + return (self.positional + ([self.varargs] if self.varargs else []) + + self.kwonlyargs + ([self.kwargs] if self.kwargs else [])) + + def resolve(self, arguments, variables=None, resolve_named=True, + resolve_variables_until=None, dict_to_kwargs=False): + resolver = ArgumentResolver(self, resolve_named, + resolve_variables_until, dict_to_kwargs) + positional, named = resolver.resolve(arguments, variables) + if self.types or self.defaults: + converter = ArgumentConverter(self, dry_run=not variables) + positional, named = converter.convert(positional, named) + return positional, named + + def map(self, positional, named, replace_defaults=True): + mapper = ArgumentMapper(self) + return mapper.map(positional, named, replace_defaults) diff --git a/robot/lib/python3.8/site-packages/robot/running/arguments/argumentvalidator.py b/robot/lib/python3.8/site-packages/robot/running/arguments/argumentvalidator.py new file mode 100644 index 0000000000000000000000000000000000000000..d22cdca5ff9dfd7409e2a71924b58675cbe1aaeb --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/arguments/argumentvalidator.py @@ -0,0 +1,87 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.utils import plural_or_not, seq2str +from robot.variables import is_list_variable + + +class ArgumentValidator(object): + + def __init__(self, argspec): + """:type argspec: :py:class:`robot.running.arguments.ArgumentSpec`""" + self._argspec = argspec + + def validate(self, positional, named, dryrun=False): + if dryrun and any(is_list_variable(arg) for arg in positional): + return + named = set(name for name, value in named) + self._validate_no_multiple_values(positional, named, self._argspec) + self._validate_positional_limits(positional, named, self._argspec) + self._validate_no_mandatory_missing(positional, named, self._argspec) + self._validate_no_named_only_missing(named, self._argspec) + self._validate_no_extra_named(named, self._argspec) + + def _validate_positional_limits(self, positional, named, spec): + count = len(positional) + self._named_positionals(named, spec) + if not spec.minargs <= count <= spec.maxargs: + self._raise_wrong_count(count, spec) + + def _named_positionals(self, named, spec): + if not spec.supports_named: + return 0 + return sum(1 for n in named if n in spec.positional) + + def _raise_wrong_count(self, count, spec): + minend = plural_or_not(spec.minargs) + if spec.minargs == spec.maxargs: + expected = '%d argument%s' % (spec.minargs, minend) + elif not spec.varargs: + expected = '%d to %d arguments' % (spec.minargs, spec.maxargs) + else: + expected = 'at least %d argument%s' % (spec.minargs, minend) + if spec.kwargs or spec.kwonlyargs: + expected = expected.replace('argument', 'non-named argument') + raise DataError("%s '%s' expected %s, got %d." + % (spec.type, spec.name, expected, count)) + + def _validate_no_multiple_values(self, positional, named, spec): + if named and spec.supports_named: + for name in spec.positional[:len(positional)]: + if name in named: + raise DataError("%s '%s' got multiple values for argument " + "'%s'." % (spec.type, spec.name, name)) + + def _validate_no_mandatory_missing(self, positional, named, spec): + for name in spec.positional[len(positional):spec.minargs]: + if name not in named: + raise DataError("%s '%s' missing value for argument '%s'." + % (spec.type, spec.name, name)) + + def _validate_no_named_only_missing(self, named, spec): + defined = set(named) | set(spec.defaults) + missing = [arg for arg in spec.kwonlyargs if arg not in defined] + if missing: + raise DataError("%s '%s' missing named-only argument%s %s." + % (spec.type, spec.name, plural_or_not(missing), + seq2str(sorted(missing)))) + + def _validate_no_extra_named(self, named, spec): + if not spec.kwargs: + extra = set(named) - set(spec.positional) - set(spec.kwonlyargs) + if extra: + raise DataError("%s '%s' got unexpected named argument%s %s." + % (spec.type, spec.name, plural_or_not(extra), + seq2str(sorted(extra)))) diff --git a/robot/lib/python3.8/site-packages/robot/running/arguments/embedded.py b/robot/lib/python3.8/site-packages/robot/running/arguments/embedded.py new file mode 100644 index 0000000000000000000000000000000000000000..2e006c0bd2392c8914cf493e7b654fe53336a896 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/arguments/embedded.py @@ -0,0 +1,92 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +from robot.errors import DataError +from robot.utils import get_error_message, py2to3 +from robot.variables import VariableIterator + + +@py2to3 +class EmbeddedArguments(object): + + def __init__(self, name): + if '${' in name: + self.name, self.args = EmbeddedArgumentParser().parse(name) + else: + self.name, self.args = None, [] + + def __nonzero__(self): + return self.name is not None + + +class EmbeddedArgumentParser(object): + _regexp_extension = re.compile(r'(? -1 and not self._passing_list(arguments): + arguments[self._index:] = [arguments[self._index:]] + return arguments + + def _passing_list(self, arguments): + return self._correct_count(arguments) and is_list_like(arguments[-1]) + + def _correct_count(self, arguments): + return len(arguments) == self._index + 1 diff --git a/robot/lib/python3.8/site-packages/robot/running/arguments/typeconverters.py b/robot/lib/python3.8/site-packages/robot/running/arguments/typeconverters.py new file mode 100644 index 0000000000000000000000000000000000000000..811ae14c60cacec1c2948e3c660970a0962e96aa --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/arguments/typeconverters.py @@ -0,0 +1,335 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ast import literal_eval +from collections import OrderedDict +try: + from collections import abc +except ImportError: # Python 2 + import collections as abc +from datetime import datetime, date, timedelta +from decimal import InvalidOperation, Decimal +try: + from enum import Enum +except ImportError: # Standard in Py 3.4+ but can be separately installed + class Enum(object): + pass +from numbers import Integral, Real + +from robot.libraries.DateTime import convert_date, convert_time +from robot.utils import (FALSE_STRINGS, IRONPYTHON, TRUE_STRINGS, PY_VERSION, + PY2, eq, seq2str, type_name, unicode) + + +class TypeConverter(object): + type = None + abc = None + aliases = () + convert_none = True + _converters = OrderedDict() + _type_aliases = {} + + @property + def type_name(self): + return self.type.__name__.lower() + + @classmethod + def register(cls, converter_class): + converter = converter_class() + cls._converters[converter.type] = converter + for name in (converter.type_name,) + converter.aliases: + if name is not None: + cls._type_aliases[name.lower()] = converter.type + return converter_class + + @classmethod + def converter_for(cls, type_): + # Types defined in the typing module in Python 3.7+. For details see + # https://bugs.python.org/issue34568 + if PY_VERSION >= (3, 7) and hasattr(type_, '__origin__'): + type_ = type_.__origin__ + if isinstance(type_, (str, unicode)): + try: + type_ = cls._type_aliases[type_.lower()] + except KeyError: + return None + if not isinstance(type_, type) or issubclass(type_, unicode): + return None + if type_ in cls._converters: + return cls._converters[type_] + for converter in cls._converters.values(): + if converter.handles(type_): + return converter.get_converter(type_) + return None + + def handles(self, type_): + return (issubclass(type_, self.type) or + self.abc and issubclass(type_, self.abc)) + + def get_converter(self, type_): + return self + + def convert(self, name, value, explicit_type=True): + if self.convert_none and value.upper() == 'NONE': + return None + try: + return self._convert(value, explicit_type) + except ValueError as error: + return self._handle_error(name, value, error, explicit_type) + + def _convert(self, value, explicit_type=True): + raise NotImplementedError + + def _handle_error(self, name, value, error, explicit_type=True): + if not explicit_type: + return value + ending = u': %s' % error if error.args else '.' + raise ValueError("Argument '%s' got value '%s' that cannot be " + "converted to %s%s" + % (name, value, self.type_name, ending)) + + def _literal_eval(self, value, expected): + # ast.literal_eval has some issues with sets: + if expected is set: + # On Python 2 it doesn't handle sets at all. + if PY2: + raise ValueError('Sets are not supported on Python 2.') + # There is no way to define an empty set. + if value == 'set()': + return set() + try: + value = literal_eval(value) + except (ValueError, SyntaxError): + # Original errors aren't too informative in these cases. + raise ValueError('Invalid expression.') + except TypeError as err: + raise ValueError('Evaluating expression failed: %s' % err) + if not isinstance(value, expected): + raise ValueError('Value is %s, not %s.' % (type_name(value), + expected.__name__)) + return value + + +@TypeConverter.register +class BooleanConverter(TypeConverter): + type = bool + type_name = 'boolean' + aliases = ('bool',) + + def _convert(self, value, explicit_type=True): + upper = value.upper() + if upper in TRUE_STRINGS: + return True + if upper in FALSE_STRINGS: + return False + return value + + +@TypeConverter.register +class IntegerConverter(TypeConverter): + type = int + abc = Integral + type_name = 'integer' + aliases = ('int', 'long') + + def _convert(self, value, explicit_type=True): + try: + return int(value) + except ValueError: + if not explicit_type: + try: + return float(value) + except ValueError: + pass + raise ValueError + + +@TypeConverter.register +class FloatConverter(TypeConverter): + type = float + abc = Real + aliases = ('double',) + + def _convert(self, value, explicit_type=True): + try: + return float(value) + except ValueError: + raise ValueError + + +@TypeConverter.register +class DecimalConverter(TypeConverter): + type = Decimal + + def _convert(self, value, explicit_type=True): + try: + return Decimal(value) + except InvalidOperation: + # With Python 3 error messages by decimal module are not very + # useful and cannot be included in our error messages: + # https://bugs.python.org/issue26208 + raise ValueError + + +@TypeConverter.register +class BytesConverter(TypeConverter): + type = bytes + abc = getattr(abc, 'ByteString', None) # ByteString is new in Python 3 + type_name = 'bytes' # Needed on Python 2 + convert_none = False + + def _convert(self, value, explicit_type=True): + if PY2 and not explicit_type: + return value + try: + value = value.encode('latin-1') + except UnicodeEncodeError as err: + raise ValueError("Character '%s' cannot be mapped to a byte." + % value[err.start:err.start+1]) + return value if not IRONPYTHON else bytes(value) + + +@TypeConverter.register +class ByteArrayConverter(TypeConverter): + type = bytearray + convert_none = False + + def _convert(self, value, explicit_type=True): + try: + return bytearray(value, 'latin-1') + except UnicodeEncodeError as err: + raise ValueError("Character '%s' cannot be mapped to a byte." + % value[err.start:err.start+1]) + + +@TypeConverter.register +class DateTimeConverter(TypeConverter): + type = datetime + + def _convert(self, value, explicit_type=True): + return convert_date(value, result_format='datetime') + + +@TypeConverter.register +class DateConverter(TypeConverter): + type = date + + def _convert(self, value, explicit_type=True): + dt = convert_date(value, result_format='datetime') + if dt.hour or dt.minute or dt.second or dt.microsecond: + raise ValueError("Value is datetime, not date.") + return dt.date() + + +@TypeConverter.register +class TimeDeltaConverter(TypeConverter): + type = timedelta + + def _convert(self, value, explicit_type=True): + return convert_time(value, result_format='timedelta') + + +@TypeConverter.register +class EnumConverter(TypeConverter): + type = Enum + + def __init__(self, enum=None): + self._enum = enum + + @property + def type_name(self): + return self._enum.__name__ if self._enum else None + + def get_converter(self, type_): + return EnumConverter(type_) + + def _convert(self, value, explicit_type=True): + try: + # This is compatible with the enum module in Python 3.4, its + # enum34 backport, and the older enum module. `self._enum[value]` + # wouldn't work with the old enum module. + return getattr(self._enum, value) + except AttributeError: + members = sorted(self._get_members(self._enum)) + matches = [m for m in members if eq(m, value, ignore='_')] + if not matches: + raise ValueError("%s does not have member '%s'. Available: %s" + % (self.type_name, value, seq2str(members))) + if len(matches) > 1: + raise ValueError("%s has multiple members matching '%s'. Available: %s" + % (self.type_name, value, seq2str(matches))) + return getattr(self._enum, matches[0]) + + def _get_members(self, enum): + try: + return list(enum.__members__) + except AttributeError: # old enum module + return [attr for attr in dir(enum) if not attr.startswith('_')] + + +@TypeConverter.register +class NoneConverter(TypeConverter): + type = type(None) + + def _convert(self, value, explicit_type=True): + return value + + +@TypeConverter.register +class ListConverter(TypeConverter): + type = list + abc = abc.Sequence + + def _convert(self, value, explicit_type=True): + return self._literal_eval(value, list) + + +@TypeConverter.register +class TupleConverter(TypeConverter): + type = tuple + + def _convert(self, value, explicit_type=True): + return self._literal_eval(value, tuple) + + +@TypeConverter.register +class DictionaryConverter(TypeConverter): + type = dict + abc = abc.Mapping + type_name = 'dictionary' + aliases = ('dict', 'map') + + def _convert(self, value, explicit_type=True): + return self._literal_eval(value, dict) + + +@TypeConverter.register +class SetConverter(TypeConverter): + type = set + abc = abc.Set + + def _convert(self, value, explicit_type=True): + return self._literal_eval(value, set) + + +@TypeConverter.register +class FrozenSetConverter(TypeConverter): + type = frozenset + + def _convert(self, value, explicit_type=True): + # There are issues w/ literal_eval. See self._literal_eval for details. + if value == 'frozenset()' and not PY2: + return frozenset() + return frozenset(self._literal_eval(value, set)) diff --git a/robot/lib/python3.8/site-packages/robot/running/arguments/typevalidator.py b/robot/lib/python3.8/site-packages/robot/running/arguments/typevalidator.py new file mode 100644 index 0000000000000000000000000000000000000000..56a1c0f002c05e2cfe6fd218b1ad16cb8fbc1718 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/arguments/typevalidator.py @@ -0,0 +1,56 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.utils import (is_dict_like, is_list_like, plural_or_not as s, + seq2str, type_name) + + +class TypeValidator(object): + + def __init__(self, argspec): + """:type argspec: :py:class:`robot.running.arguments.ArgumentSpec`""" + self._argspec = argspec + + def validate(self, types): + if types is None: + return None + if not types: + return {} + if is_dict_like(types): + return self.validate_type_dict(types) + if is_list_like(types): + return self.convert_type_list_to_dict(types) + raise DataError('Type information must be given as a dictionary or ' + 'a list, got %s.' % type_name(types)) + + def validate_type_dict(self, types): + # 'return' isn't used for anything yet but it may be shown by Libdoc + # in the future. Trying to be forward compatible. + names = set(self._argspec.argument_names + ['return']) + extra = [t for t in types if t not in names] + if extra: + raise DataError('Type information given to non-existing ' + 'argument%s %s.' + % (s(extra), seq2str(sorted(extra)))) + return types + + def convert_type_list_to_dict(self, types): + names = self._argspec.argument_names + if len(types) > len(names): + raise DataError('Type information given to %d argument%s but ' + 'keyword has only %d argument%s.' + % (len(types), s(types), len(names), s(names))) + return {name: value for name, value in zip(names, types) if value} diff --git a/robot/lib/python3.8/site-packages/robot/running/builder/__init__.py b/robot/lib/python3.8/site-packages/robot/running/builder/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cfe3cdf8ea150259697044e7755364f5368e1cb7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/builder/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .builders import TestSuiteBuilder, ResourceFileBuilder +from .parsers import RobotParser diff --git a/robot/lib/python3.8/site-packages/robot/running/builder/builders.py b/robot/lib/python3.8/site-packages/robot/running/builder/builders.py new file mode 100644 index 0000000000000000000000000000000000000000..a0ad2e92213bb68b2068bc04e72d8a01e7b6f948 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/builder/builders.py @@ -0,0 +1,211 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from robot.errors import DataError +from robot.output import LOGGER +from robot.parsing import SuiteStructureBuilder, SuiteStructureVisitor + +from .parsers import RobotParser, NoInitFileDirectoryParser, RestParser +from .testsettings import TestDefaults + + +class TestSuiteBuilder(object): + """Builder to construct ``TestSuite`` objects based on data on the disk. + + The :meth:`build` method constructs executable + :class:`~robot.running.model.TestSuite` objects based on test data files + or directories. There are two main use cases for this API: + + - Execute the created suite by using its + :meth:`~robot.running.model.TestSuite.run` method. The suite can be + can be modified before execution if needed. + + - Inspect the suite to see, for example, what tests it has or what tags + tests have. This can be more convenient than using the lower level + :mod:`~robot.parsing` APIs but does not allow saving modified data + back to the disk. + + Both modifying the suite and inspecting what data it contains are easiest + done by using the :mod:`~robot.model.visitor` interface. + + This class is part of the public API and should be imported via the + :mod:`robot.api` package. + """ + + def __init__(self, included_suites=None, included_extensions=('robot',), + rpa=None, allow_empty_suite=False, process_curdir=True): + """ + :param include_suites: + List of suite names to include. If ``None`` or an empty list, + all suites are included. Same as using :option:`--suite` on + the command line. + :param included_extensions: + List of extensions of files to parse. Same as :option:`--extension`. + This parameter was named ``extension`` before RF 3.2. + :param rpa: Explicit test execution mode. ``True`` for RPA and + ``False`` for test automation. By default mode is got from test + data headers and possible conflicting headers cause an error. + Same as :option:`--rpa` or :option:`--norpa`. + :param allow_empty_suite: + Specify is it an error if the built suite contains no tests. + Same as :option:`--runemptysuite`. New in RF 3.2. + :param process_curdir: + Control processing the special ``${CURDIR}`` variable. It is + resolved already at parsing time by default, but that can be + changed by giving this argument ``False`` value. New in RF 3.2. + """ + self.rpa = rpa + self.included_suites = included_suites + self.included_extensions = included_extensions + self.allow_empty_suite = allow_empty_suite + self.process_curdir = process_curdir + + def build(self, *paths): + """ + :param paths: Paths to test data files or directories. + :return: :class:`~robot.running.model.TestSuite` instance. + """ + structure = SuiteStructureBuilder(self.included_extensions, + self.included_suites).build(paths) + parser = SuiteStructureParser(self.included_extensions, + self.rpa, self.process_curdir) + suite = parser.parse(structure) + if not self.included_suites and not self.allow_empty_suite: + self._validate_test_counts(suite, multisource=len(paths) > 1) + suite.remove_empty_suites(preserve_direct_children=len(paths) > 1) + return suite + + def _validate_test_counts(self, suite, multisource=False): + def validate(suite): + if not suite.has_tests: + raise DataError("Suite '%s' contains no tests or tasks." + % suite.name) + if not multisource: + validate(suite) + else: + for s in suite.suites: + validate(s) + + +class SuiteStructureParser(SuiteStructureVisitor): + + def __init__(self, included_extensions, rpa=None, process_curdir=True): + self.rpa = rpa + self._rpa_given = rpa is not None + self.suite = None + self._stack = [] + self.parsers = self._get_parsers(included_extensions, process_curdir) + + def _get_parsers(self, extensions, process_curdir): + robot_parser = RobotParser(process_curdir) + rest_parser = RestParser(process_curdir) + parsers = { + None: NoInitFileDirectoryParser(), + 'robot': robot_parser, + 'rst': rest_parser, + 'rest': rest_parser + } + for ext in extensions: + if ext not in parsers: + parsers[ext] = robot_parser + return parsers + + def _get_parser(self, extension): + try: + return self.parsers[extension] + except KeyError: + return self.parsers['robot'] + + def parse(self, structure): + structure.visit(self) + self.suite.rpa = self.rpa + return self.suite + + def visit_file(self, structure): + LOGGER.info("Parsing file '%s'." % structure.source) + suite, _ = self._build_suite(structure) + if self._stack: + self._stack[-1][0].suites.append(suite) + else: + self.suite = suite + + def start_directory(self, structure): + if structure.source: + LOGGER.info("Parsing directory '%s'." % structure.source) + suite, defaults = self._build_suite(structure) + if self.suite is None: + self.suite = suite + else: + self._stack[-1][0].suites.append(suite) + self._stack.append((suite, defaults)) + + def end_directory(self, structure): + suite, _ = self._stack.pop() + if suite.rpa is None and suite.suites: + suite.rpa = suite.suites[0].rpa + + def _build_suite(self, structure): + parent_defaults = self._stack[-1][-1] if self._stack else None + source = structure.source + defaults = TestDefaults(parent_defaults) + parser = self._get_parser(structure.extension) + try: + if structure.is_directory: + suite = parser.parse_init_file(structure.init_file or source, defaults) + else: + suite = parser.parse_suite_file(source, defaults) + if not suite.tests: + LOGGER.info("Data source '%s' has no tests or tasks." % source) + self._validate_execution_mode(suite) + except DataError as err: + raise DataError("Parsing '%s' failed: %s" % (source, err.message)) + return suite, defaults + + def _validate_execution_mode(self, suite): + if self._rpa_given: + suite.rpa = self.rpa + elif suite.rpa is None: + pass + elif self.rpa is None: + self.rpa = suite.rpa + elif self.rpa is not suite.rpa: + this, that = ('tasks', 'tests') if suite.rpa else ('tests', 'tasks') + raise DataError("Conflicting execution modes. File has %s " + "but files parsed earlier have %s. Fix headers " + "or use '--rpa' or '--norpa' options to set the " + "execution mode explicitly." % (this, that)) + + +class ResourceFileBuilder(object): + + def __init__(self, process_curdir=True): + self.process_curdir = process_curdir + + def build(self, source): + LOGGER.info("Parsing resource file '%s'." % source) + resource = self._parse(source) + if resource.imports or resource.variables or resource.keywords: + LOGGER.info("Imported resource file '%s' (%d keywords)." + % (source, len(resource.keywords))) + else: + LOGGER.warn("Imported resource file '%s' is empty." % source) + return resource + + def _parse(self, source): + if os.path.splitext(source)[1].lower() in ('.rst', '.rest'): + return RestParser(self.process_curdir).parse_resource_file(source) + return RobotParser(self.process_curdir).parse_resource_file(source) diff --git a/robot/lib/python3.8/site-packages/robot/running/builder/parsers.py b/robot/lib/python3.8/site-packages/robot/running/builder/parsers.py new file mode 100644 index 0000000000000000000000000000000000000000..b8869bfb62dec9fc548d20450a1dd499a1be26e1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/builder/parsers.py @@ -0,0 +1,142 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from ast import NodeVisitor + +from robot.errors import DataError +from robot.output import LOGGER +from robot.parsing import get_model, get_resource_model, get_init_model, Token +from robot.utils import FileReader, read_rest_data + +from .testsettings import TestDefaults +from .transformers import SuiteBuilder, SettingsBuilder, ResourceBuilder +from ..model import TestSuite, ResourceFile + + +class BaseParser(object): + + def parse_init_file(self, source, defaults=None): + raise NotImplementedError + + def parse_suite_file(self, source, defaults=None): + raise NotImplementedError + + def parse_resource_file(self, source): + raise NotImplementedError + + +class RobotParser(BaseParser): + + def __init__(self, process_curdir=True): + self.process_curdir = process_curdir + + def parse_init_file(self, source, defaults=None): + directory = os.path.dirname(source) + suite = TestSuite(name=format_name(directory), source=directory) + return self._build(suite, source, defaults, get_model=get_init_model) + + def parse_suite_file(self, source, defaults=None): + suite = TestSuite(name=format_name(source), source=source) + return self._build(suite, source, defaults) + + def build_suite(self, model, name=None, defaults=None): + source = model.source + suite = TestSuite(name=name or format_name(source), source=source) + return self._build(suite, source, defaults, model) + + def _build(self, suite, source, defaults, model=None, get_model=get_model): + if defaults is None: + defaults = TestDefaults() + if model is None: + model = get_model(self._get_source(source), data_only=True, + curdir=self._get_curdir(source)) + ErrorReporter(source).visit(model) + SettingsBuilder(suite, defaults).visit(model) + SuiteBuilder(suite, defaults).visit(model) + suite.rpa = self._get_rpa_mode(model) + return suite + + def _get_curdir(self, source): + if not self.process_curdir: + return None + return os.path.dirname(source).replace('\\', '\\\\') + + def _get_source(self, source): + return source + + def parse_resource_file(self, source): + model = get_resource_model(self._get_source(source), data_only=True, + curdir=self._get_curdir(source)) + resource = ResourceFile(source=source) + ErrorReporter(source).visit(model) + ResourceBuilder(resource).visit(model) + return resource + + def _get_rpa_mode(self, data): + if not data: + return None + tasks = [s.tasks for s in data.sections if hasattr(s, 'tasks')] + if all(tasks) or not any(tasks): + return tasks[0] if tasks else None + raise DataError('One file cannot have both tests and tasks.') + + +class RestParser(RobotParser): + + def _get_source(self, source): + with FileReader(source) as reader: + return read_rest_data(reader) + + +class NoInitFileDirectoryParser(BaseParser): + + def parse_init_file(self, source, defaults=None): + return TestSuite(name=format_name(source), source=source) + + +def format_name(source): + def strip_possible_prefix_from_name(name): + return name.split('__', 1)[-1] + + def format_name(name): + name = strip_possible_prefix_from_name(name) + name = name.replace('_', ' ').strip() + return name.title() if name.islower() else name + + if source is None: + return None + if os.path.isdir(source): + basename = os.path.basename(source) + else: + basename = os.path.splitext(os.path.basename(source))[0] + return format_name(basename) + + +class ErrorReporter(NodeVisitor): + + def __init__(self, source): + self.source = source + + def visit_Error(self, node): + fatal = node.get_token(Token.FATAL_ERROR) + if fatal: + raise DataError(self._format_message(fatal)) + for error in node.get_tokens(Token.ERROR): + LOGGER.error(self._format_message(error)) + + def _format_message(self, token): + return ("Error in file '%s' on line %s: %s" + % (self.source, token.lineno, token.error)) diff --git a/robot/lib/python3.8/site-packages/robot/running/builder/testsettings.py b/robot/lib/python3.8/site-packages/robot/running/builder/testsettings.py new file mode 100644 index 0000000000000000000000000000000000000000..545877edb6a3d7ea51b2a2151708cfde157846ee --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/builder/testsettings.py @@ -0,0 +1,136 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +NOTSET = object() + + +class TestDefaults(object): + + def __init__(self, parent=None): + self.parent = parent + self._setup = None + self._teardown = None + self._force_tags = () + self.default_tags = () + self.template = None + self._timeout = None + + @property + def setup(self): + if self._setup: + return self._setup + if self.parent: + return self.parent.setup + return None + + @setup.setter + def setup(self, setup): + self._setup = setup + + @property + def teardown(self): + if self._teardown: + return self._teardown + if self.parent: + return self.parent.teardown + return None + + @teardown.setter + def teardown(self, teardown): + self._teardown = teardown + + @property + def force_tags(self): + parent_force_tags = self.parent.force_tags if self.parent else () + return self._force_tags + parent_force_tags + + @force_tags.setter + def force_tags(self, force_tags): + self._force_tags = force_tags + + @property + def timeout(self): + if self._timeout: + return self._timeout + if self.parent: + return self.parent.timeout + return None + + @timeout.setter + def timeout(self, timeout): + self._timeout = timeout + + +class TestSettings(object): + + def __init__(self, defaults): + self.defaults = defaults + self._setup = NOTSET + self._teardown = NOTSET + self._timeout = NOTSET + self._template = NOTSET + self._tags = NOTSET + + @property + def setup(self): + if self._setup is NOTSET: + return self.defaults.setup + return self._setup + + @setup.setter + def setup(self, setup): + self._setup = setup + + @property + def teardown(self): + if self._teardown is NOTSET: + return self.defaults.teardown + return self._teardown + + @teardown.setter + def teardown(self, teardown): + self._teardown = teardown + + @property + def timeout(self): + if self._timeout is NOTSET: + return self.defaults.timeout + return self._timeout + + @timeout.setter + def timeout(self, timeout): + self._timeout = timeout + + @property + def template(self): + if self._template is NOTSET: + return self.defaults.template + return self._template + + @template.setter + def template(self, template): + self._template = template + + @property + def tags(self): + if self._tags is NOTSET: + tags = self.defaults.default_tags + else: + tags = self._tags + return tags + self.defaults.force_tags + + @tags.setter + def tags(self, tags): + self._tags = tags diff --git a/robot/lib/python3.8/site-packages/robot/running/builder/transformers.py b/robot/lib/python3.8/site-packages/robot/running/builder/transformers.py new file mode 100644 index 0000000000000000000000000000000000000000..c5fd50b5c81a7c2184d8922c285557b2eea2ffe2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/builder/transformers.py @@ -0,0 +1,265 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ast import NodeVisitor + +from robot.variables import VariableIterator + +from ..model import ForLoop, Keyword +from .testsettings import TestSettings + + +def fixture(node, fixture_type): + if node.name is None: + return None + return Keyword(node.name, args=node.args, type=fixture_type, + lineno=node.lineno) + + +class SettingsBuilder(NodeVisitor): + + def __init__(self, suite, test_defaults): + self.suite = suite + self.test_defaults = test_defaults + + def visit_Documentation(self, node): + self.suite.doc = node.value + + def visit_Metadata(self, node): + self.suite.metadata[node.name] = node.value + + def visit_SuiteSetup(self, node): + self.suite.keywords.setup = fixture(node, Keyword.SETUP_TYPE) + + def visit_SuiteTeardown(self, node): + self.suite.keywords.teardown = fixture(node, Keyword.TEARDOWN_TYPE) + + def visit_TestSetup(self, node): + self.test_defaults.setup = fixture(node, Keyword.SETUP_TYPE) + + def visit_TestTeardown(self, node): + self.test_defaults.teardown = fixture(node, Keyword.TEARDOWN_TYPE) + + def visit_TestTimeout(self, node): + self.test_defaults.timeout = node.value + + def visit_DefaultTags(self, node): + self.test_defaults.default_tags = node.values + + def visit_ForceTags(self, node): + self.test_defaults.force_tags = node.values + + def visit_TestTemplate(self, node): + self.test_defaults.template = node.value + + def visit_ResourceImport(self, node): + self.suite.resource.imports.create(type='Resource', name=node.name, + lineno=node.lineno) + + def visit_LibraryImport(self, node): + self.suite.resource.imports.create(type='Library', name=node.name, + args=node.args, alias=node.alias, + lineno=node.lineno) + + def visit_VariablesImport(self, node): + self.suite.resource.imports.create(type='Variables', name=node.name, + args=node.args, lineno=node.lineno) + + def visit_VariableSection(self, node): + pass + + def visit_TestCaseSection(self, node): + pass + + def visit_KeywordSection(self, node): + pass + + +class SuiteBuilder(NodeVisitor): + + def __init__(self, suite, test_defaults): + self.suite = suite + self.test_defaults = test_defaults + + def visit_SettingSection(self, node): + pass + + def visit_Variable(self, node): + self.suite.resource.variables.create(name=node.name, value=node.value, + lineno=node.lineno, error=node.error) + + def visit_TestCase(self, node): + TestCaseBuilder(self.suite, self.test_defaults).visit(node) + + def visit_Keyword(self, node): + KeywordBuilder(self.suite.resource).visit(node) + + +class ResourceBuilder(NodeVisitor): + + def __init__(self, resource): + self.resource = resource + + def visit_Documentation(self, node): + self.resource.doc = node.value + + def visit_LibraryImport(self, node): + self.resource.imports.create(type='Library', name=node.name, + args=node.args, alias=node.alias, + lineno=node.lineno) + + def visit_ResourceImport(self, node): + self.resource.imports.create(type='Resource', name=node.name, + lineno=node.lineno) + + def visit_VariablesImport(self, node): + self.resource.imports.create(type='Variables', name=node.name, + args=node.args, lineno=node.lineno) + + def visit_Variable(self, node): + self.resource.variables.create(name=node.name, value=node.value, + lineno=node.lineno, error=node.error) + def visit_Keyword(self, node): + KeywordBuilder(self.resource).visit(node) + + +class TestCaseBuilder(NodeVisitor): + + def __init__(self, suite, defaults): + self.suite = suite + self.settings = TestSettings(defaults) + self.test = None + + def visit_TestCase(self, node): + self.test = self.suite.tests.create(name=node.name, lineno=node.lineno) + self.generic_visit(node) + self._set_settings(self.test, self.settings) + + def _set_settings(self, test, settings): + test.keywords.setup = settings.setup + test.keywords.teardown = settings.teardown + test.timeout = settings.timeout + test.tags = settings.tags + if settings.template: + test.template = settings.template + self._set_template(test, settings.template) + + def _set_template(self, parent, template): + for kw in parent.keywords: + if kw.type == kw.FOR_LOOP_TYPE: + self._set_template(kw, template) + elif kw.type == kw.KEYWORD_TYPE: + name, args = self._format_template(template, kw.args) + kw.name = name + kw.args = args + + def _format_template(self, template, arguments): + variables = VariableIterator(template, identifiers='$') + count = len(variables) + if count == 0 or count != len(arguments): + return template, arguments + temp = [] + for (before, _, after), arg in zip(variables, arguments): + temp.extend([before, arg]) + temp.append(after) + return ''.join(temp), () + + def visit_ForLoop(self, node): + # Header and end used only for deprecation purposes. Remove in RF 3.3! + loop = ForLoop(node.variables, node.values, node.flavor, node.lineno, + node._header, node._end) + ForLoopBuilder(loop).visit(node) + self.test.keywords.append(loop) + + def visit_TemplateArguments(self, node): + self.test.keywords.create(args=node.args, lineno=node.lineno) + + def visit_Documentation(self, node): + self.test.doc = node.value + + def visit_Setup(self, node): + self.settings.setup = fixture(node, Keyword.SETUP_TYPE) + + def visit_Teardown(self, node): + self.settings.teardown = fixture(node, Keyword.TEARDOWN_TYPE) + + def visit_Timeout(self, node): + self.settings.timeout = node.value + + def visit_Tags(self, node): + self.settings.tags = node.values + + def visit_Template(self, node): + self.settings.template = node.value + + def visit_KeywordCall(self, node): + self.test.keywords.create(name=node.keyword, args=node.args, + assign=node.assign, lineno=node.lineno) + + +class KeywordBuilder(NodeVisitor): + + def __init__(self, resource): + self.resource = resource + self.kw = None + self.teardown = None + + def visit_Keyword(self, node): + self.kw = self.resource.keywords.create(name=node.name, + lineno=node.lineno) + self.generic_visit(node) + self.kw.keywords.teardown = self.teardown + + def visit_Documentation(self, node): + self.kw.doc = node.value + + def visit_Arguments(self, node): + self.kw.args = node.values + + def visit_Tags(self, node): + self.kw.tags = node.values + + def visit_Return(self, node): + self.kw.return_ = node.values + + def visit_Timeout(self, node): + self.kw.timeout = node.value + + def visit_Teardown(self, node): + self.teardown = fixture(node, Keyword.TEARDOWN_TYPE) + + def visit_KeywordCall(self, node): + self.kw.keywords.create(name=node.keyword, args=node.args, + assign=node.assign, lineno=node.lineno) + + def visit_ForLoop(self, node): + # Header and end used only for deprecation purposes. Remove in RF 3.3! + loop = ForLoop(node.variables, node.values, node.flavor, node.lineno, + node._header, node._end) + ForLoopBuilder(loop).visit(node) + self.kw.keywords.append(loop) + + +class ForLoopBuilder(NodeVisitor): + + def __init__(self, loop): + self.loop = loop + + def visit_KeywordCall(self, node): + self.loop.keywords.create(name=node.keyword, args=node.args, + assign=node.assign, lineno=node.lineno) + + def visit_TemplateArguments(self, node): + self.loop.keywords.create(args=node.args, lineno=node.lineno) diff --git a/robot/lib/python3.8/site-packages/robot/running/context.py b/robot/lib/python3.8/site-packages/robot/running/context.py new file mode 100644 index 0000000000000000000000000000000000000000..87f51bc1a3524140589172bff0af68f2bd433cbc --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/context.py @@ -0,0 +1,198 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from contextlib import contextmanager + +from robot.errors import DataError +from robot.utils import unic + + +class ExecutionContexts(object): + + def __init__(self): + self._contexts = [] + + @property + def current(self): + return self._contexts[-1] if self._contexts else None + + @property + def top(self): + return self._contexts[0] if self._contexts else None + + def __iter__(self): + return iter(self._contexts) + + @property + def namespaces(self): + return (context.namespace for context in self) + + def start_suite(self, suite, namespace, output, dry_run=False): + ctx = _ExecutionContext(suite, namespace, output, dry_run) + self._contexts.append(ctx) + return ctx + + def end_suite(self): + self._contexts.pop() + + +# This is ugly but currently needed e.g. by BuiltIn +EXECUTION_CONTEXTS = ExecutionContexts() + + +class _ExecutionContext(object): + _started_keywords_threshold = 42 # Jython on Windows don't work with higher + + def __init__(self, suite, namespace, output, dry_run=False): + self.suite = suite + self.test = None + self.timeouts = set() + self.namespace = namespace + self.output = output + self.dry_run = dry_run + self.in_suite_teardown = False + self.in_test_teardown = False + self.in_keyword_teardown = 0 + self._started_keywords = 0 + self.timeout_occurred = False + + @contextmanager + def suite_teardown(self): + self.in_suite_teardown = True + try: + yield + finally: + self.in_suite_teardown = False + + @contextmanager + def test_teardown(self, test): + self.variables.set_test('${TEST_STATUS}', test.status) + self.variables.set_test('${TEST_MESSAGE}', test.message) + self.in_test_teardown = True + self._remove_timeout(test.timeout) + try: + yield + finally: + self.in_test_teardown = False + + @contextmanager + def keyword_teardown(self, error): + self.variables.set_keyword('${KEYWORD_STATUS}', 'FAIL' if error else 'PASS') + self.variables.set_keyword('${KEYWORD_MESSAGE}', unic(error or '')) + self.in_keyword_teardown += 1 + try: + yield + finally: + self.in_keyword_teardown -= 1 + + @property + @contextmanager + def user_keyword(self): + self.namespace.start_user_keyword() + try: + yield + finally: + self.namespace.end_user_keyword() + + @contextmanager + def timeout(self, timeout): + self._add_timeout(timeout) + try: + yield + finally: + self._remove_timeout(timeout) + + @property + def in_teardown(self): + return bool(self.in_suite_teardown or + self.in_test_teardown or + self.in_keyword_teardown) + + @property + def variables(self): + return self.namespace.variables + + def end_suite(self, suite): + for name in ['${PREV_TEST_NAME}', + '${PREV_TEST_STATUS}', + '${PREV_TEST_MESSAGE}']: + self.variables.set_global(name, self.variables[name]) + self.output.end_suite(suite) + self.namespace.end_suite(suite) + EXECUTION_CONTEXTS.end_suite() + + def set_suite_variables(self, suite): + self.variables['${SUITE_NAME}'] = suite.longname + self.variables['${SUITE_SOURCE}'] = suite.source or '' + self.variables['${SUITE_DOCUMENTATION}'] = suite.doc + self.variables['${SUITE_METADATA}'] = suite.metadata.copy() + + def report_suite_status(self, status, message): + self.variables['${SUITE_STATUS}'] = status + self.variables['${SUITE_MESSAGE}'] = message + + def start_test(self, test): + self.test = test + self._add_timeout(test.timeout) + self.namespace.start_test() + self.variables.set_test('${TEST_NAME}', test.name) + self.variables.set_test('${TEST_DOCUMENTATION}', test.doc) + self.variables.set_test('@{TEST_TAGS}', list(test.tags)) + + def _add_timeout(self, timeout): + if timeout: + timeout.start() + self.timeouts.add(timeout) + + def _remove_timeout(self, timeout): + if timeout in self.timeouts: + self.timeouts.remove(timeout) + + def end_test(self, test): + self.test = None + self._remove_timeout(test.timeout) + self.namespace.end_test() + self.variables.set_suite('${PREV_TEST_NAME}', test.name) + self.variables.set_suite('${PREV_TEST_STATUS}', test.status) + self.variables.set_suite('${PREV_TEST_MESSAGE}', test.message) + self.timeout_occurred = False + + def start_keyword(self, keyword): + self._started_keywords += 1 + if self._started_keywords > self._started_keywords_threshold: + raise DataError('Maximum limit of started keywords exceeded.') + self.output.start_keyword(keyword) + + def end_keyword(self, keyword): + self.output.end_keyword(keyword) + self._started_keywords -= 1 + + def get_runner(self, name): + return self.namespace.get_runner(name) + + def trace(self, message): + self.output.trace(message) + + def debug(self, message): + self.output.debug(message) + + def info(self, message): + self.output.info(message) + + def warn(self, message): + self.output.warn(message) + + def fail(self, message): + self.output.fail(message) diff --git a/robot/lib/python3.8/site-packages/robot/running/dynamicmethods.py b/robot/lib/python3.8/site-packages/robot/running/dynamicmethods.py new file mode 100644 index 0000000000000000000000000000000000000000..909360544b3b4d780e985a074bab81ac71327b2f --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/dynamicmethods.py @@ -0,0 +1,174 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.utils import (get_error_message, is_java_method, is_bytes, + is_list_like, is_unicode, type_name, py2to3) + +from .arguments import JavaArgumentParser, PythonArgumentParser + + +def no_dynamic_method(*args): + return None + + +@py2to3 +class _DynamicMethod(object): + _underscore_name = NotImplemented + + def __init__(self, lib): + self.method = self._get_method(lib) + + def _get_method(self, lib): + for name in self._underscore_name, self._camelCaseName: + method = getattr(lib, name, None) + if callable(method): + return method + return no_dynamic_method + + @property + def _camelCaseName(self): + tokens = self._underscore_name.split('_') + return ''.join([tokens[0]] + [t.capitalize() for t in tokens[1:]]) + + @property + def name(self): + return self.method.__name__ + + def __call__(self, *args): + try: + return self._handle_return_value(self.method(*args)) + except: + raise DataError("Calling dynamic method '%s' failed: %s" + % (self.name, get_error_message())) + + def _handle_return_value(self, value): + raise NotImplementedError + + def _to_string(self, value, allow_tuple=False, allow_none=False): + if is_unicode(value): + return value + if is_bytes(value): + return value.decode('UTF-8') + if allow_tuple and is_list_like(value) and len(value) > 0: + return tuple(value) + if allow_none and value is None: + return value + or_tuple = ' or a non-empty tuple' if allow_tuple else '' + raise DataError('Return value must be a string%s, got %s.' + % (or_tuple, type_name(value))) + + def _to_list(self, value): + if value is None: + return () + if not is_list_like(value): + raise DataError + return value + + def _to_list_of_strings(self, value, allow_tuples=False): + try: + return [self._to_string(item, allow_tuples) + for item in self._to_list(value)] + except DataError: + raise DataError('Return value must be a list of strings%s.' + % (' or non-empty tuples' if allow_tuples else '')) + + def __nonzero__(self): + return self.method is not no_dynamic_method + + +class GetKeywordNames(_DynamicMethod): + _underscore_name = 'get_keyword_names' + + def _handle_return_value(self, value): + names = self._to_list_of_strings(value) + return list(self._remove_duplicates(names)) + + def _remove_duplicates(self, names): + seen = set() + for name in names: + if name not in seen: + seen.add(name) + yield name + + +class RunKeyword(_DynamicMethod): + _underscore_name = 'run_keyword' + + @property + def supports_kwargs(self): + if is_java_method(self.method): + return self._supports_java_kwargs(self.method) + return self._supports_python_kwargs(self.method) + + def _supports_python_kwargs(self, method): + spec = PythonArgumentParser().parse(method) + return len(spec.positional) == 3 + + def _supports_java_kwargs(self, method): + func = self.method.im_func if hasattr(method, 'im_func') else method + signatures = func.argslist[:func.nargs] + spec = JavaArgumentParser().parse(signatures) + return (self._java_single_signature_kwargs(spec) or + self._java_multi_signature_kwargs(spec)) + + def _java_single_signature_kwargs(self, spec): + return len(spec.positional) == 1 and spec.varargs and spec.kwargs + + def _java_multi_signature_kwargs(self, spec): + return len(spec.positional) == 3 and not (spec.varargs or spec.kwargs) + + +class GetKeywordDocumentation(_DynamicMethod): + _underscore_name = 'get_keyword_documentation' + + def _handle_return_value(self, value): + return self._to_string(value or '') + + +class GetKeywordArguments(_DynamicMethod): + _underscore_name = 'get_keyword_arguments' + + def __init__(self, lib): + _DynamicMethod.__init__(self, lib) + self._supports_kwargs = RunKeyword(lib).supports_kwargs + + def _handle_return_value(self, value): + if value is None: + if self._supports_kwargs: + return ['*varargs', '**kwargs'] + return ['*varargs'] + return self._to_list_of_strings(value, allow_tuples=True) + + +class GetKeywordTypes(_DynamicMethod): + _underscore_name = 'get_keyword_types' + + def _handle_return_value(self, value): + return value if self else {} + + +class GetKeywordTags(_DynamicMethod): + _underscore_name = 'get_keyword_tags' + + def _handle_return_value(self, value): + return self._to_list_of_strings(value) + + +class GetKeywordSource(_DynamicMethod): + _underscore_name = 'get_keyword_source' + + def _handle_return_value(self, value): + return self._to_string(value, allow_none=True) diff --git a/robot/lib/python3.8/site-packages/robot/running/handlers.py b/robot/lib/python3.8/site-packages/robot/running/handlers.py new file mode 100644 index 0000000000000000000000000000000000000000..9dac8d67defef7892803fa0921d4a0a9bf313cd5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/handlers.py @@ -0,0 +1,360 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from copy import copy +import inspect + +from robot.utils import (getdoc, getshortdoc, is_java_init, is_java_method, + is_list_like, normpath, printable_name, + split_tags_from_doc, type_name, unwrap) +from robot.errors import DataError +from robot.model import Tags + +from .arguments import (ArgumentSpec, DynamicArgumentParser, + JavaArgumentCoercer, JavaArgumentParser, + PythonArgumentParser) +from .dynamicmethods import GetKeywordSource, GetKeywordTypes +from .librarykeywordrunner import (EmbeddedArgumentsRunner, + LibraryKeywordRunner, RunKeywordRunner) +from .runkwregister import RUN_KW_REGISTER + + +def Handler(library, name, method): + if RUN_KW_REGISTER.is_run_keyword(library.orig_name, name): + return _RunKeywordHandler(library, name, method) + if is_java_method(method): + return _JavaHandler(library, name, method) + else: + return _PythonHandler(library, name, method) + + +def DynamicHandler(library, name, method, doc, argspec, tags=None): + if RUN_KW_REGISTER.is_run_keyword(library.orig_name, name): + return _DynamicRunKeywordHandler(library, name, method, doc, argspec, tags) + return _DynamicHandler(library, name, method, doc, argspec, tags) + + +def InitHandler(library, method=None, docgetter=None): + Init = _PythonInitHandler if not is_java_init(method) else _JavaInitHandler + return Init(library, '__init__', method, docgetter) + + +class _RunnableHandler(object): + + def __init__(self, library, handler_name, handler_method, doc='', tags=None): + self.library = library + self._handler_name = handler_name + self.name = self._get_name(handler_name, handler_method) + self.arguments = self._parse_arguments(handler_method) + self._method = self._get_initial_handler(library, handler_name, + handler_method) + doc, tags_from_doc = split_tags_from_doc(doc or '') + tags_from_attr = self._get_tags_from_attribute(handler_method) + self._doc = doc + self.tags = Tags(tuple(tags_from_doc) + + tuple(tags_from_attr) + + tuple(tags or ())) + + def _get_name(self, handler_name, handler_method): + robot_name = getattr(handler_method, 'robot_name', None) + name = robot_name or printable_name(handler_name, code_style=True) + if not name: + raise DataError('Keyword name cannot be empty.') + return name + + def _parse_arguments(self, handler_method): + raise NotImplementedError + + def _get_tags_from_attribute(self, handler_method): + tags = getattr(handler_method, 'robot_tags', ()) + if not is_list_like(tags): + raise DataError("Expected tags to be list-like, got %s." + % type_name(tags)) + return tags + + def _get_initial_handler(self, library, name, method): + if library.scope.is_global: + return self._get_global_handler(method, name) + return None + + def resolve_arguments(self, args, variables=None): + return self.arguments.resolve(args, variables) + + @property + def doc(self): + return self._doc + + @property + def longname(self): + return '%s.%s' % (self.library.name, self.name) + + @property + def shortdoc(self): + return getshortdoc(self.doc) + + @property + def libname(self): + return self.library.name + + @property + def source(self): + return self.library.source + + @property + def lineno(self): + return -1 + + def create_runner(self, name): + return LibraryKeywordRunner(self) + + def current_handler(self): + if self._method: + return self._method + return self._get_handler(self.library.get_instance(), self._handler_name) + + def _get_global_handler(self, method, name): + return method + + def _get_handler(self, lib_instance, handler_name): + try: + return getattr(lib_instance, handler_name) + except AttributeError: + # Occurs with old-style classes. + if handler_name == '__init__': + return None + raise + + +class _PythonHandler(_RunnableHandler): + + def __init__(self, library, handler_name, handler_method): + _RunnableHandler.__init__(self, library, handler_name, handler_method, + getdoc(handler_method)) + + def _parse_arguments(self, handler_method): + return PythonArgumentParser().parse(handler_method, self.longname) + + @property + def source(self): + handler = self.current_handler() + # `getsourcefile` can return None and raise TypeError. + try: + source = inspect.getsourcefile(unwrap(handler)) + except TypeError: + source = None + return normpath(source) if source else self.library.source + + @property + def lineno(self): + handler = self.current_handler() + try: + lines, start_lineno = inspect.getsourcelines(unwrap(handler)) + except (TypeError, OSError, IOError): + return -1 + for increment, line in enumerate(lines): + if line.strip().startswith('def '): + return start_lineno + increment + return start_lineno + + +class _JavaHandler(_RunnableHandler): + + def __init__(self, library, handler_name, handler_method): + _RunnableHandler.__init__(self, library, handler_name, handler_method) + signatures = self._get_signatures(handler_method) + self._arg_coercer = JavaArgumentCoercer(signatures, self.arguments) + + def _parse_arguments(self, handler_method): + signatures = self._get_signatures(handler_method) + return JavaArgumentParser().parse(signatures, self.longname) + + def _get_signatures(self, handler): + code_object = getattr(handler, 'im_func', handler) + return code_object.argslist[:code_object.nargs] + + def resolve_arguments(self, args, variables=None): + positional, named = self.arguments.resolve(args, variables, + dict_to_kwargs=True) + arguments = self._arg_coercer.coerce(positional, named, + dryrun=not variables) + return arguments, [] + + +class _DynamicHandler(_RunnableHandler): + + def __init__(self, library, handler_name, dynamic_method, doc='', + argspec=None, tags=None): + self._argspec = argspec + self._run_keyword_method_name = dynamic_method.name + self._supports_kwargs = dynamic_method.supports_kwargs + _RunnableHandler.__init__(self, library, handler_name, + dynamic_method.method, doc, tags) + self._source_info = None + + def _parse_arguments(self, handler_method): + spec = DynamicArgumentParser().parse(self._argspec, self.longname) + if not self._supports_kwargs: + if spec.kwargs: + raise DataError("Too few '%s' method parameters for **kwargs " + "support." % self._run_keyword_method_name) + if spec.kwonlyargs: + raise DataError("Too few '%s' method parameters for " + "keyword-only arguments support." + % self._run_keyword_method_name) + get_keyword_types = GetKeywordTypes(self.library.get_instance()) + spec.types = get_keyword_types(self._handler_name) + return spec + + @property + def source(self): + if self._source_info is None: + self._source_info = self._get_source_info() + return self._source_info[0] + + def _get_source_info(self): + get_keyword_source = GetKeywordSource(self.library.get_instance()) + try: + source = get_keyword_source(self._handler_name) + except DataError as err: + self.library.report_error( + "Getting source information for keyword '%s' failed: %s" + % (self.name, err.message), err.details + ) + return None, -1 + if not source: + return self.library.source, -1 + if ':' not in source: + return source, -1 + path, lineno = source.rsplit(':', 1) + try: + return path or self.library.source, int(lineno) + except ValueError: + return source, -1 + + @property + def lineno(self): + if self._source_info is None: + self._source_info = self._get_source_info() + return self._source_info[1] + + def resolve_arguments(self, arguments, variables=None): + positional, named = self.arguments.resolve(arguments, variables) + if not self._supports_kwargs: + positional, named = self.arguments.map(positional, named) + return positional, named + + def _get_handler(self, lib_instance, handler_name): + runner = getattr(lib_instance, self._run_keyword_method_name) + return self._get_dynamic_handler(runner, handler_name) + + def _get_global_handler(self, method, name): + return self._get_dynamic_handler(method, name) + + def _get_dynamic_handler(self, runner, name): + def handler(*positional, **kwargs): + if self._supports_kwargs: + return runner(name, positional, kwargs) + else: + return runner(name, positional) + return handler + + +class _RunKeywordHandler(_PythonHandler): + + def create_runner(self, name): + default_dry_run_keywords = ('name' in self.arguments.positional and + self._args_to_process) + return RunKeywordRunner(self, default_dry_run_keywords) + + @property + def _args_to_process(self): + return RUN_KW_REGISTER.get_args_to_process(self.library.orig_name, + self.name) + + def resolve_arguments(self, args, variables=None): + args_to_process = self._args_to_process + return self.arguments.resolve(args, variables, resolve_named=False, + resolve_variables_until=args_to_process) + + +class _DynamicRunKeywordHandler(_DynamicHandler, _RunKeywordHandler): + _parse_arguments = _RunKeywordHandler._parse_arguments + resolve_arguments = _RunKeywordHandler.resolve_arguments + + +class _PythonInitHandler(_PythonHandler): + + def __init__(self, library, handler_name, handler_method, docgetter): + _PythonHandler.__init__(self, library, handler_name, handler_method) + self._docgetter = docgetter + + @property + def doc(self): + if self._docgetter: + self._doc = self._docgetter() or self._doc + self._docgetter = None + return self._doc + + def _parse_arguments(self, init_method): + parser = PythonArgumentParser(type='Test Library') + return parser.parse(init_method or (lambda: None), self.library.name) + + +class _JavaInitHandler(_JavaHandler): + + def __init__(self, library, handler_name, handler_method, docgetter): + _JavaHandler.__init__(self, library, handler_name, handler_method) + self._docgetter = docgetter + + @property + def doc(self): + if self._docgetter: + self._doc = self._docgetter() or self._doc + self._docgetter = None + return self._doc + + def _parse_arguments(self, handler_method): + parser = JavaArgumentParser(type='Test Library') + signatures = self._get_signatures(handler_method) + return parser.parse(signatures, self.library.name) + + +class EmbeddedArgumentsHandler(object): + + def __init__(self, name_regexp, orig_handler): + self.arguments = ArgumentSpec() # Show empty argument spec for Libdoc + self.name_regexp = name_regexp + self._orig_handler = orig_handler + + def __getattr__(self, item): + return getattr(self._orig_handler, item) + + @property + def library(self): + return self._orig_handler.library + + @library.setter + def library(self, library): + self._orig_handler.library = library + + def matches(self, name): + return self.name_regexp.match(name) is not None + + def create_runner(self, name): + return EmbeddedArgumentsRunner(self, name) + + def __copy__(self): + orig_handler = copy(self._orig_handler) + return EmbeddedArgumentsHandler(self.name_regexp, orig_handler) diff --git a/robot/lib/python3.8/site-packages/robot/running/handlerstore.py b/robot/lib/python3.8/site-packages/robot/running/handlerstore.py new file mode 100644 index 0000000000000000000000000000000000000000..656f359732e3f1187bb1e5fb2ae51cdbfb3db5f7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/handlerstore.py @@ -0,0 +1,85 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from operator import attrgetter + +from robot.errors import DataError, KeywordError +from robot.utils import NormalizedDict + +from .usererrorhandler import UserErrorHandler + + +class HandlerStore(object): + TEST_LIBRARY_TYPE = 'Test library' + TEST_CASE_FILE_TYPE = 'Test case file' + RESOURCE_FILE_TYPE = 'Resource file' + + def __init__(self, source, source_type): + self.source = source + self.source_type = source_type + self._normal = NormalizedDict(ignore='_') + self._embedded = [] + + def add(self, handler, embedded=False): + if embedded: + self._embedded.append(handler) + elif handler.name not in self._normal: + self._normal[handler.name] = handler + else: + error = DataError('Keyword with same name defined multiple times.') + self._normal[handler.name] = UserErrorHandler(error, handler.name, + handler.libname) + raise error + + def __iter__(self): + handlers = list(self._normal.values()) + self._embedded + return iter(sorted(handlers, key=attrgetter('name'))) + + def __len__(self): + return len(self._normal) + len(self._embedded) + + def __contains__(self, name): + if name in self._normal: + return True + return any(template.matches(name) for template in self._embedded) + + def create_runner(self, name): + return self[name].create_runner(name) + + def __getitem__(self, name): + try: + return self._normal[name] + except KeyError: + return self._find_embedded(name) + + def _find_embedded(self, name): + embedded = [template for template in self._embedded + if template.matches(name)] + if len(embedded) == 1: + return embedded[0] + self._raise_no_single_match(name, embedded) + + def _raise_no_single_match(self, name, found): + if self.source_type == self.TEST_CASE_FILE_TYPE: + source = self.source_type + else: + source = "%s '%s'" % (self.source_type, self.source) + if not found: + raise KeywordError("%s contains no keywords matching name '%s'." + % (source, name)) + error = ["%s contains multiple keywords matching name '%s':" + % (source, name)] + names = sorted(handler.name for handler in found) + raise KeywordError('\n '.join(error + names)) diff --git a/robot/lib/python3.8/site-packages/robot/running/importer.py b/robot/lib/python3.8/site-packages/robot/running/importer.py new file mode 100644 index 0000000000000000000000000000000000000000..db52991a4caf8864d953a635f38cbe4500a2183f --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/importer.py @@ -0,0 +1,161 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy +import os.path +import os + +from robot.output import LOGGER +from robot.errors import FrameworkError, DataError +from robot.utils import normpath, seq2str, seq2str2, is_string + +from .builder import ResourceFileBuilder +from .handlerstore import HandlerStore +from .testlibraries import TestLibrary + + +RESOURCE_EXTENSIONS = ('.resource', '.robot', '.txt', '.tsv', '.rst', '.rest') + + +class Importer(object): + + def __init__(self): + self._library_cache = ImportCache() + self._resource_cache = ImportCache() + + def reset(self): + self.__init__() + + def close_global_library_listeners(self): + for lib in self._library_cache.values(): + lib.close_global_listeners() + + def import_library(self, name, args, alias, variables): + lib = TestLibrary(name, args, variables, create_handlers=False) + positional, named = lib.positional_args, lib.named_args + lib = self._import_library(name, positional, named, lib) + if alias: + alias = variables.replace_scalar(alias) + lib = self._copy_library(lib, alias) + LOGGER.info("Imported library '%s' with name '%s'" % (name, alias)) + return lib + + def import_resource(self, path): + self._validate_resource_extension(path) + if path in self._resource_cache: + LOGGER.info("Found resource file '%s' from cache" % path) + else: + resource = ResourceFileBuilder().build(path) + self._resource_cache[path] = resource + return self._resource_cache[path] + + def _validate_resource_extension(self, path): + extension = os.path.splitext(path)[1] + if extension.lower() not in RESOURCE_EXTENSIONS: + raise DataError("Invalid resource file extension '%s'. " + "Supported extensions are %s." + % (extension, seq2str(RESOURCE_EXTENSIONS))) + + def _import_library(self, name, positional, named, lib): + args = positional + ['%s=%s' % arg for arg in named] + key = (name, positional, named) + if key in self._library_cache: + LOGGER.info("Found test library '%s' with arguments %s from cache" + % (name, seq2str2(args))) + return self._library_cache[key] + lib.create_handlers() + self._library_cache[key] = lib + self._log_imported_library(name, args, lib) + return lib + + def _log_imported_library(self, name, args, lib): + type = lib.__class__.__name__.replace('Library', '').lower()[1:] + listener = ', with listener' if lib.has_listener else '' + LOGGER.info("Imported library '%s' with arguments %s " + "(version %s, %s type, %s scope, %d keywords%s)" + % (name, seq2str2(args), lib.version or '', + type, lib.scope, len(lib), listener)) + if not lib and not lib.has_listener: + LOGGER.warn("Imported library '%s' contains no keywords." % name) + + def _copy_library(self, orig, name): + # This is pretty ugly. Hopefully we can remove cache and copying + # altogether in 3.0 and always just re-import libraries: + # https://github.com/robotframework/robotframework/issues/2106 + # Could then also remove __copy__ methods added to some handlers as + # a workaround for this IronPython bug: + # https://github.com/IronLanguages/main/issues/1192 + lib = copy.copy(orig) + lib.name = name + lib.scope = type(lib.scope)(lib) + lib.reset_instance() + lib.handlers = HandlerStore(orig.handlers.source, + orig.handlers.source_type) + for handler in orig.handlers._normal.values(): + handler = copy.copy(handler) + handler.library = lib + lib.handlers.add(handler) + for handler in orig.handlers._embedded: + handler = copy.copy(handler) + handler.library = lib + lib.handlers.add(handler, embedded=True) + return lib + + +class ImportCache(object): + """Keeps track on and optionally caches imported items. + + Handles paths in keys case-insensitively on case-insensitive OSes. + Unlike dicts, this storage accepts mutable values in keys. + """ + + def __init__(self): + self._keys = [] + self._items = [] + + def __setitem__(self, key, item): + if not is_string(key) and not isinstance(key, tuple): + raise FrameworkError('Invalid key for ImportCache') + key = self._norm_path_key(key) + if key not in self._keys: + self._keys.append(key) + self._items.append(item) + else: + self._items[self._keys.index(key)] = item + + def add(self, key, item=None): + self.__setitem__(key, item) + + def __getitem__(self, key): + key = self._norm_path_key(key) + if key not in self._keys: + raise KeyError + return self._items[self._keys.index(key)] + + def __contains__(self, key): + return self._norm_path_key(key) in self._keys + + def values(self): + return self._items + + def _norm_path_key(self, key): + if self._is_path(key): + return normpath(key, case_normalize=True) + if isinstance(key, tuple): + return tuple(self._norm_path_key(k) for k in key) + return key + + def _is_path(self, key): + return is_string(key) and os.path.isabs(key) and os.path.exists(key) diff --git a/robot/lib/python3.8/site-packages/robot/running/librarykeywordrunner.py b/robot/lib/python3.8/site-packages/robot/running/librarykeywordrunner.py new file mode 100644 index 0000000000000000000000000000000000000000..78e06a18c79ad8da7ea8159ec56526de3a27d39b --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/librarykeywordrunner.py @@ -0,0 +1,230 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError +from robot.model import Keywords +from robot.output import LOGGER +from robot.result import Keyword as KeywordResult +from robot.utils import prepr, unic +from robot.variables import (contains_variable, is_list_variable, + VariableAssignment) + +from .steprunner import StepRunner +from .model import Keyword +from .outputcapture import OutputCapturer +from .signalhandler import STOP_SIGNAL_MONITOR +from .statusreporter import StatusReporter + + +class LibraryKeywordRunner(object): + _executed_in_dry_run = ('BuiltIn.Import Library', + 'BuiltIn.Set Library Search Order') + + def __init__(self, handler, name=None): + self._handler = handler + self.name = name or handler.name + self.pre_run_messages = None + + @property + def library(self): + return self._handler.library + + @property + def libname(self): + return self._handler.library.name + + @property + def longname(self): + return '%s.%s' % (self.library.name, self.name) + + def run(self, kw, context): + assignment = VariableAssignment(kw.assign) + with StatusReporter(context, self._get_result(kw, assignment)): + with assignment.assigner(context) as assigner: + return_value = self._run(context, kw.args) + assigner.assign(return_value) + return return_value + + def _get_result(self, kw, assignment): + handler = self._handler + return KeywordResult(kwname=self.name, + libname=handler.libname, + doc=handler.shortdoc, + args=kw.args, + assign=tuple(assignment), + tags=handler.tags, + type=kw.type) + + def _run(self, context, args): + if self.pre_run_messages: + for message in self.pre_run_messages: + context.output.message(message) + positional, named = \ + self._handler.resolve_arguments(args, context.variables) + context.output.trace(lambda: self._trace_log_args(positional, named)) + runner = self._runner_for(context, self._handler.current_handler(), + positional, dict(named)) + return self._run_with_output_captured_and_signal_monitor(runner, context) + + def _trace_log_args(self, positional, named): + args = [prepr(arg) for arg in positional] + args += ['%s=%s' % (unic(n), prepr(v)) for n, v in named] + return 'Arguments: [ %s ]' % ' | '.join(args) + + def _runner_for(self, context, handler, positional, named): + timeout = self._get_timeout(context) + if timeout and timeout.active: + def runner(): + with LOGGER.delayed_logging: + context.output.debug(timeout.get_message) + return timeout.run(handler, args=positional, kwargs=named) + return runner + return lambda: handler(*positional, **named) + + def _get_timeout(self, context): + return min(context.timeouts) if context.timeouts else None + + def _run_with_output_captured_and_signal_monitor(self, runner, context): + with OutputCapturer(): + return self._run_with_signal_monitoring(runner, context) + + def _run_with_signal_monitoring(self, runner, context): + try: + STOP_SIGNAL_MONITOR.start_running_keyword(context.in_teardown) + return runner() + finally: + STOP_SIGNAL_MONITOR.stop_running_keyword() + + def dry_run(self, kw, context): + assignment = VariableAssignment(kw.assign) + result = self._get_result(kw, assignment) + with StatusReporter(context, result, dry_run_lib_kw=True): + assignment.validate_assignment() + self._dry_run(context, kw.args) + + def _dry_run(self, context, args): + if self._handler.longname in self._executed_in_dry_run: + self._run(context, args) + else: + self._handler.resolve_arguments(args) + + +class EmbeddedArgumentsRunner(LibraryKeywordRunner): + + def __init__(self, handler, name): + LibraryKeywordRunner.__init__(self, handler, name) + self._embedded_args = handler.name_regexp.match(name).groups() + + def _run(self, context, args): + if args: + raise DataError("Positional arguments are not allowed when using " + "embedded arguments.") + return LibraryKeywordRunner._run(self, context, self._embedded_args) + + def _dry_run(self, context, args): + return LibraryKeywordRunner._dry_run(self, context, self._embedded_args) + + +class RunKeywordRunner(LibraryKeywordRunner): + + def __init__(self, handler, default_dry_run_keywords=False): + LibraryKeywordRunner.__init__(self, handler) + self._default_dry_run_keywords = default_dry_run_keywords + + # TODO: Should this be removed altogether? + # - Doesn't seem to be really needed. + # - Not used with dynamic run kws in the new design (at least currently) + def _get_timeout(self, namespace): + return None + + def _run_with_output_captured_and_signal_monitor(self, runner, context): + return self._run_with_signal_monitoring(runner, context) + + def _dry_run(self, context, args): + LibraryKeywordRunner._dry_run(self, context, args) + keywords = self._get_runnable_dry_run_keywords(args) + StepRunner(context).run_steps(keywords) + + def _get_runnable_dry_run_keywords(self, args): + keywords = Keywords() + for keyword in self._get_dry_run_keywords(args): + if contains_variable(keyword.name): + continue + keywords.append(keyword) + return keywords + + def _get_dry_run_keywords(self, args): + name = self._handler.name + if name == 'Run Keyword If': + return list(self._get_run_kw_if_keywords(args)) + if name == 'Run Keywords': + return list(self._get_run_kws_keywords(args)) + if self._default_dry_run_keywords: + return self._get_default_run_kw_keywords(args) + return [] + + def _get_run_kw_if_keywords(self, given_args): + for kw_call in self._get_run_kw_if_calls(given_args): + if kw_call: + yield Keyword(name=kw_call[0], args=kw_call[1:]) + + def _get_run_kw_if_calls(self, given_args): + while 'ELSE IF' in given_args: + kw_call, given_args = self._split_run_kw_if_args(given_args, 'ELSE IF', 2) + yield kw_call + if 'ELSE' in given_args: + kw_call, else_call = self._split_run_kw_if_args(given_args, 'ELSE', 1) + yield kw_call + yield else_call + elif self._validate_kw_call(given_args): + expr, kw_call = given_args[0], given_args[1:] + if not is_list_variable(expr): + yield kw_call + + def _split_run_kw_if_args(self, given_args, control_word, required_after): + index = list(given_args).index(control_word) + expr_and_call = given_args[:index] + remaining = given_args[index+1:] + if not (self._validate_kw_call(expr_and_call) and + self._validate_kw_call(remaining, required_after)): + raise DataError("Invalid 'Run Keyword If' usage.") + if is_list_variable(expr_and_call[0]): + return (), remaining + return expr_and_call[1:], remaining + + def _validate_kw_call(self, kw_call, min_length=2): + if len(kw_call) >= min_length: + return True + return any(is_list_variable(item) for item in kw_call) + + def _get_run_kws_keywords(self, given_args): + for kw_call in self._get_run_kws_calls(given_args): + yield Keyword(name=kw_call[0], args=kw_call[1:]) + + def _get_run_kws_calls(self, given_args): + if 'AND' not in given_args: + for kw_call in given_args: + yield [kw_call,] + else: + while 'AND' in given_args: + index = list(given_args).index('AND') + kw_call, given_args = given_args[:index], given_args[index + 1:] + yield kw_call + if given_args: + yield given_args + + def _get_default_run_kw_keywords(self, given_args): + index = list(self._handler.arguments.positional).index('name') + return [Keyword(name=given_args[index], args=given_args[index+1:])] diff --git a/robot/lib/python3.8/site-packages/robot/running/libraryscopes.py b/robot/lib/python3.8/site-packages/robot/running/libraryscopes.py new file mode 100644 index 0000000000000000000000000000000000000000..a19faf2ad6d906d9dc7fb901ec251a433c761628 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/libraryscopes.py @@ -0,0 +1,97 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect + +from robot.utils import normalize, unic + + +def LibraryScope(libcode, library): + scope = _get_scope(libcode) + if scope == 'GLOBAL': + return GlobalScope(library) + if scope in ('SUITE', 'TESTSUITE'): + return TestSuiteScope(library) + return TestCaseScope(library) + + +def _get_scope(libcode): + if inspect.ismodule(libcode): + return 'GLOBAL' + scope = getattr(libcode, 'ROBOT_LIBRARY_SCOPE', '') + return normalize(unic(scope), ignore='_').upper() + + +class GlobalScope(object): + is_global = True + + def __init__(self, library): + self._register_listeners = library.register_listeners + self._unregister_listeners = library.unregister_listeners + + def start_suite(self): + self._register_listeners() + + def end_suite(self): + self._unregister_listeners() + + def start_test(self): + pass + + def end_test(self): + pass + + def __str__(self): + return 'GLOBAL' + + +class TestSuiteScope(GlobalScope): + is_global = False + + def __init__(self, library): + GlobalScope.__init__(self, library) + self._reset_instance = library.reset_instance + self._instance_cache = [] + + def start_suite(self): + prev = self._reset_instance() + self._instance_cache.append(prev) + self._register_listeners() + + def end_suite(self): + self._unregister_listeners(close=True) + prev = self._instance_cache.pop() + self._reset_instance(prev) + + def __str__(self): + return 'SUITE' + + +class TestCaseScope(TestSuiteScope): + + def start_test(self): + self._unregister_listeners() + prev = self._reset_instance() + self._instance_cache.append(prev) + self._register_listeners() + + def end_test(self): + self._unregister_listeners(close=True) + prev = self._instance_cache.pop() + self._reset_instance(prev) + self._register_listeners() + + def __str__(self): + return 'TEST' diff --git a/robot/lib/python3.8/site-packages/robot/running/model.py b/robot/lib/python3.8/site-packages/robot/running/model.py new file mode 100644 index 0000000000000000000000000000000000000000..0c0465f31f8125f1ce0b3dc350a680980132f2a9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/model.py @@ -0,0 +1,371 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module implementing test execution related model objects. + +When tests are executed normally, these objects are created based on the test +data on the file system by :class:`~.builder.TestSuiteBuilder`, but external +tools can also create an executable test suite model structure directly. +Regardless the approach to create it, the model is executed by calling +:meth:`~TestSuite.run` method of the root test suite. See the +:mod:`robot.running` package level documentation for more information and +examples. + +The most important classes defined in this module are :class:`TestSuite`, +:class:`TestCase` and :class:`Keyword`. When tests are executed, these objects +can be inspected and modified by `pre-run modifiers`__ and `listeners`__. +The aforementioned objects are considered stable, but other objects in this +module may still be changed in the future major releases. + +__ http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#programmatic-modification-of-results +__ http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#listener-interface +""" + +import os + +from robot import model +from robot.conf import RobotSettings +from robot.output import LOGGER, Output, pyloggingconf +from robot.utils import seq2str, setter + +from .randomizer import Randomizer +from .steprunner import StepRunner + + +class Keyword(model.Keyword): + """Represents a single executable keyword. + + These keywords never have child keywords or messages. The actual keyword + that is executed depends on the context where this model is executed. + + See the base class for documentation of attributes not documented here. + """ + __slots__ = ['lineno'] + message_class = None #: Internal usage only. + + def __init__(self, name='', doc='', args=(), assign=(), tags=(), + timeout=None, type=model.Keyword.KEYWORD_TYPE, lineno=None): + model.Keyword.__init__(self, name, doc, args, assign, tags, timeout, type) + self.lineno = lineno + + def run(self, context): + """Execute the keyword. + + Typically called internally by :meth:`TestSuite.run`. + """ + return StepRunner(context).run_step(self) + + +class ForLoop(Keyword): + """Represents a for loop in test data. + + Contains keywords in the loop body as child :attr:`keywords`. + """ + __slots__ = ['flavor', 'lineno', '_header', '_end'] + keyword_class = Keyword #: Internal usage only. + + def __init__(self, variables, values, flavor, lineno=None, + _header='FOR', _end='END'): + Keyword.__init__(self, assign=variables, args=values, + type=Keyword.FOR_LOOP_TYPE) + self.flavor = flavor + self.lineno = lineno + self._header = _header + self._end = _end + + @property + def variables(self): + return self.assign + + @property + def values(self): + return self.args + + def __unicode__(self): + variables = ' '.join(self.assign) + values = ' '.join(self.values) + return u'FOR %s %s %s' % (variables, self.flavor, values) + + +class TestCase(model.TestCase): + """Represents a single executable test case. + + See the base class for documentation of attributes not documented here. + """ + __slots__ = ['template', 'lineno'] + keyword_class = Keyword #: Internal usage only. + + def __init__(self, name='', doc='', tags=None, timeout=None, template=None, + lineno=None): + model.TestCase.__init__(self, name, doc, tags, timeout) + #: Name of the keyword that has been used as template + #: when building the test. ``None`` if no is template used. + self.template = template + self.lineno = lineno + + +class TestSuite(model.TestSuite): + """Represents a single executable test suite. + + See the base class for documentation of attributes not documented here. + """ + __slots__ = ['resource'] + test_class = TestCase #: Internal usage only. + keyword_class = Keyword #: Internal usage only. + + def __init__(self, name='', doc='', metadata=None, source=None, rpa=None): + model.TestSuite.__init__(self, name, doc, metadata, source, rpa) + #: :class:`ResourceFile` instance containing imports, variables and + #: keywords the suite owns. When data is parsed from the file system, + #: this data comes from the same test case file that creates the suite. + self.resource = ResourceFile(source=source) + + @classmethod + def from_file_system(cls, *paths, **config): + """Create a :class:`TestSuite` object based on the given ``paths``. + + ``paths`` are file or directory paths where to read the data from. + + Internally utilizes the :class:`~.builders.TestSuiteBuilder` class + and ``config`` can be used to configure how it is initialized. + + New in Robot Framework 3.2. + """ + from .builder import TestSuiteBuilder + return TestSuiteBuilder(**config).build(*paths) + + @classmethod + def from_model(cls, model, name=None): + """Create a :class:`TestSuite` object based on the given ``model``. + + The model can be created by using the + :func:`~robot.parsing.parser.parser.get_model` function and possibly + modified by other tooling in the :mod:`robot.parsing` module. + + New in Robot Framework 3.2. + """ + from .builder import RobotParser + return RobotParser().build_suite(model, name) + + def configure(self, randomize_suites=False, randomize_tests=False, + randomize_seed=None, **options): + """A shortcut to configure a suite using one method call. + + Can only be used with the root test suite. + + :param randomize_xxx: Passed to :meth:`randomize`. + :param options: Passed to + :class:`~robot.model.configurer.SuiteConfigurer` that will then + set suite attributes, call :meth:`filter`, etc. as needed. + + Example:: + + suite.configure(included_tags=['smoke'], + doc='Smoke test results.') + """ + model.TestSuite.configure(self, **options) + self.randomize(randomize_suites, randomize_tests, randomize_seed) + + def randomize(self, suites=True, tests=True, seed=None): + """Randomizes the order of suites and/or tests, recursively. + + :param suites: Boolean controlling should suites be randomized. + :param tests: Boolean controlling should tests be randomized. + :param seed: Random seed. Can be given if previous random order needs + to be re-created. Seed value is always shown in logs and reports. + """ + self.visit(Randomizer(suites, tests, seed)) + + def run(self, settings=None, **options): + """Executes the suite based based the given ``settings`` or ``options``. + + :param settings: :class:`~robot.conf.settings.RobotSettings` object + to configure test execution. + :param options: Used to construct new + :class:`~robot.conf.settings.RobotSettings` object if ``settings`` + are not given. + :return: :class:`~robot.result.executionresult.Result` object with + information about executed suites and tests. + + If ``options`` are used, their names are the same as long command line + options except without hyphens. Some options are ignored (see below), + but otherwise they have the same semantics as on the command line. + Options that can be given on the command line multiple times can be + passed as lists like ``variable=['VAR1:value1', 'VAR2:value2']``. + If such an option is used only once, it can be given also as a single + string like ``variable='VAR:value'``. + + Additionally listener option allows passing object directly instead of + listener name, e.g. ``run('tests.robot', listener=Listener())``. + + To capture stdout and/or stderr streams, pass open file objects in as + special keyword arguments ``stdout`` and ``stderr``, respectively. + + Only options related to the actual test execution have an effect. + For example, options related to selecting or modifying test cases or + suites (e.g. ``--include``, ``--name``, ``--prerunmodifier``) or + creating logs and reports are silently ignored. The output XML + generated as part of the execution can be configured, though. This + includes disabling it with ``output=None``. + + Example:: + + stdout = StringIO() + result = suite.run(variable='EXAMPLE:value', + critical='regression', + output='example.xml', + exitonfailure=True, + stdout=stdout) + print(result.return_code) + + To save memory, the returned + :class:`~robot.result.executionresult.Result` object does not + have any information about the executed keywords. If that information + is needed, the created output XML file needs to be read using the + :class:`~robot.result.resultbuilder.ExecutionResult` factory method. + + See the :mod:`package level ` documentation for + more examples, including how to construct executable test suites and + how to create logs and reports based on the execution results. + + See the :func:`robot.run ` function for a higher-level + API for executing tests in files or directories. + """ + from .namespace import IMPORTER + from .signalhandler import STOP_SIGNAL_MONITOR + from .runner import Runner + + with LOGGER: + if not settings: + settings = RobotSettings(options) + LOGGER.register_console_logger(**settings.console_output_config) + with pyloggingconf.robot_handler_enabled(settings.log_level): + with STOP_SIGNAL_MONITOR: + IMPORTER.reset() + output = Output(settings) + runner = Runner(output, settings) + self.visit(runner) + output.close(runner.result) + return runner.result + + +class Variable(object): + + def __init__(self, name, value, source=None, lineno=None, error=None): + self.name = name + self.value = value + self.source = source + self.lineno = lineno + self.error = error + + def report_invalid_syntax(self, message, level='ERROR'): + source = self.source or '' + line = ' on line %s' % self.lineno if self.lineno is not None else '' + LOGGER.write("Error in file '%s'%s: Setting variable '%s' failed: %s" + % (source, line, self.name, message), level) + + +class ResourceFile(object): + + def __init__(self, doc='', source=None): + self.doc = doc + self.source = source + self.imports = [] + self.keywords = [] + self.variables = [] + + @setter + def imports(self, imports): + return Imports(self.source, imports) + + @setter + def keywords(self, keywords): + return model.ItemList(UserKeyword, {'parent': self}, items=keywords) + + @setter + def variables(self, variables): + return model.ItemList(Variable, {'source': self.source}, items=variables) + + +class UserKeyword(object): + + def __init__(self, name, args=(), doc='', tags=(), return_=None, + timeout=None, lineno=None, parent=None): + self.name = name + self.args = args + self.doc = doc + self.tags = tags + self.return_ = return_ or () + self.timeout = timeout + self.keywords = [] + self.lineno = lineno + self.parent = parent + + @setter + def keywords(self, keywords): + return model.Keywords(Keyword, self, keywords) + + @setter + def tags(self, tags): + return model.Tags(tags) + + @property + def source(self): + return self.parent.source if self.parent is not None else None + + +class Import(object): + ALLOWED_TYPES = ('Library', 'Resource', 'Variables') + + def __init__(self, type, name, args=(), alias=None, source=None, + lineno=None): + if type not in self.ALLOWED_TYPES: + raise ValueError("Invalid import type '%s'. Should be one of %s." + % (type, seq2str(self.ALLOWED_TYPES, lastsep=' or '))) + self.type = type + self.name = name + self.args = args + self.alias = alias + self.source = source + self.lineno = lineno + + @property + def directory(self): + if not self.source: + return None + if os.path.isdir(self.source): + return self.source + return os.path.dirname(self.source) + + def report_invalid_syntax(self, message, level='ERROR'): + source = self.source or '' + line = ' on line %s' % self.lineno if self.lineno is not None else '' + LOGGER.write("Error in file '%s'%s: %s" % (source, line, message), + level) + + +class Imports(model.ItemList): + + def __init__(self, source, imports=None): + model.ItemList.__init__(self, Import, {'source': source}, items=imports) + + def library(self, name, args=(), alias=None, lineno=None): + self.create('Library', name, args, alias, lineno) + + def resource(self, path, lineno=None): + self.create('Resource', path, lineno) + + def variables(self, path, args=(), lineno=None): + self.create('Variables', path, args, lineno) diff --git a/robot/lib/python3.8/site-packages/robot/running/namespace.py b/robot/lib/python3.8/site-packages/robot/running/namespace.py new file mode 100644 index 0000000000000000000000000000000000000000..2b0c770617264c381a28237764b7d6ba3cc14644 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/namespace.py @@ -0,0 +1,434 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy +import os +from collections import OrderedDict +from itertools import chain + +from robot.errors import DataError, KeywordError +from robot.libraries import STDLIBS +from robot.output import LOGGER, Message +from robot.utils import (RecommendationFinder, eq, find_file, is_string, + normalize, printable_name, seq2str2) + +from .importer import ImportCache, Importer +from .model import Import +from .runkwregister import RUN_KW_REGISTER +from .usererrorhandler import UserErrorHandler +from .userkeyword import UserLibrary + +IMPORTER = Importer() + + +class Namespace(object): + _default_libraries = ('BuiltIn', 'Reserved', 'Easter') + _library_import_by_path_endings = ('.py', '.java', '.class', '/', os.sep) + + def __init__(self, variables, suite, resource): + LOGGER.info("Initializing namespace for test suite '%s'" % suite.longname) + self.variables = variables + self._imports = resource.imports + self._kw_store = KeywordStore(resource) + self._imported_variable_files = ImportCache() + self._suite_name = suite.longname + self._running_test = False + + @property + def libraries(self): + return self._kw_store.libraries.values() + + def handle_imports(self): + self._import_default_libraries() + self._handle_imports(self._imports) + + def _import_default_libraries(self): + for name in self._default_libraries: + self.import_library(name, notify=name == 'BuiltIn') + + def _handle_imports(self, import_settings): + for item in import_settings: + try: + if not item.name: + raise DataError('%s setting requires value.' % item.type) + self._import(item) + except DataError as err: + item.report_invalid_syntax(err.message) + + def _import(self, import_setting): + action = {'Library': self._import_library, + 'Resource': self._import_resource, + 'Variables': self._import_variables}[import_setting.type] + action(import_setting) + + def import_resource(self, name, overwrite=True): + self._import_resource(Import('Resource', name), overwrite=overwrite) + + def _import_resource(self, import_setting, overwrite=False): + path = self._resolve_name(import_setting) + self._validate_not_importing_init_file(path) + if overwrite or path not in self._kw_store.resources: + resource = IMPORTER.import_resource(path) + self.variables.set_from_variable_table(resource.variables, overwrite) + user_library = UserLibrary(resource) + self._kw_store.resources[path] = user_library + self._handle_imports(resource.imports) + LOGGER.imported("Resource", user_library.name, + importer=import_setting.source, + source=path) + else: + LOGGER.info("Resource file '%s' already imported by suite '%s'" + % (path, self._suite_name)) + + def _validate_not_importing_init_file(self, path): + name = os.path.splitext(os.path.basename(path))[0] + if name.lower() == '__init__': + raise DataError("Initialization file '%s' cannot be imported as " + "a resource file." % path) + + def import_variables(self, name, args, overwrite=False): + self._import_variables(Import('Variables', name, args), overwrite) + + def _import_variables(self, import_setting, overwrite=False): + path = self._resolve_name(import_setting) + args = self._resolve_args(import_setting) + if overwrite or (path, args) not in self._imported_variable_files: + self._imported_variable_files.add((path, args)) + self.variables.set_from_file(path, args, overwrite) + LOGGER.imported("Variables", os.path.basename(path), + args=list(args), + importer=import_setting.source, + source=path) + else: + msg = "Variable file '%s'" % path + if args: + msg += " with arguments %s" % seq2str2(args) + LOGGER.info("%s already imported by suite '%s'" + % (msg, self._suite_name)) + + def import_library(self, name, args=(), alias=None, notify=True): + self._import_library(Import('Library', name, args, alias), + notify=notify) + + def _import_library(self, import_setting, notify=True): + name = self._resolve_name(import_setting) + lib = IMPORTER.import_library(name, import_setting.args, + import_setting.alias, self.variables) + if lib.name in self._kw_store.libraries: + LOGGER.info("Test library '%s' already imported by suite '%s'" + % (lib.name, self._suite_name)) + return + if notify: + LOGGER.imported("Library", lib.name, + args=list(import_setting.args), + originalname=lib.orig_name, + importer=import_setting.source, + source=lib.source) + self._kw_store.libraries[lib.name] = lib + lib.start_suite() + if self._running_test: + lib.start_test() + + def _resolve_name(self, import_setting): + name = import_setting.name + try: + name = self.variables.replace_string(name) + except DataError as err: + self._raise_replacing_vars_failed(import_setting, err) + return self._get_name(name, import_setting) + + def _raise_replacing_vars_failed(self, import_setting, err): + raise DataError("Replacing variables from setting '%s' failed: %s" + % (import_setting.type, err.message)) + + def _get_name(self, name, import_setting): + if import_setting.type == 'Library' and not self._is_library_by_path(name): + return name + return find_file(name, import_setting.directory, + file_type=import_setting.type) + + def _is_library_by_path(self, path): + return path.lower().endswith(self._library_import_by_path_endings) + + def _resolve_args(self, import_setting): + try: + return self.variables.replace_list(import_setting.args) + except DataError as err: + self._raise_replacing_vars_failed(import_setting, err) + + def set_search_order(self, new_order): + old_order = self._kw_store.search_order + self._kw_store.search_order = new_order + return old_order + + def start_test(self): + self._running_test = True + self.variables.start_test() + for lib in self.libraries: + lib.start_test() + + def end_test(self): + self.variables.end_test() + for lib in self.libraries: + lib.end_test() + self._running_test = True + + def start_suite(self): + self.variables.start_suite() + + def end_suite(self, suite): + for lib in self.libraries: + lib.end_suite() + if not suite.parent: + IMPORTER.close_global_library_listeners() + self.variables.end_suite() + + def start_user_keyword(self): + self.variables.start_keyword() + + def end_user_keyword(self): + self.variables.end_keyword() + + def get_library_instance(self, libname): + return self._kw_store.get_library(libname).get_instance() + + def get_library_instances(self): + return dict((name, lib.get_instance()) + for name, lib in self._kw_store.libraries.items()) + + def reload_library(self, libname_or_instance): + library = self._kw_store.get_library(libname_or_instance) + library.reload() + return library + + def get_runner(self, name): + try: + return self._kw_store.get_runner(name) + except DataError as error: + return UserErrorHandler(error, name) + + +class KeywordStore(object): + + def __init__(self, resource): + self.user_keywords = UserLibrary(resource, + UserLibrary.TEST_CASE_FILE_TYPE) + self.libraries = OrderedDict() + self.resources = ImportCache() + self.search_order = () + + def get_library(self, name_or_instance): + if name_or_instance is None: + raise DataError("Library can not be None.") + if is_string(name_or_instance): + return self._get_lib_by_name(name_or_instance) + return self._get_lib_by_instance(name_or_instance) + + def _get_lib_by_name(self, name): + if name in self.libraries: + return self.libraries[name] + matches = [lib for lib in self.libraries.values() if eq(lib.name, name)] + if len(matches) == 1: + return matches[0] + self._no_library_found(name, multiple=bool(matches)) + + def _no_library_found(self, name, multiple=False): + if multiple: + raise DataError("Multiple libraries matching '%s' found." % name) + raise DataError("No library '%s' found." % name) + + def _get_lib_by_instance(self, instance): + for lib in self.libraries.values(): + if lib.get_instance(create=False) is instance: + return lib + self._no_library_found(instance) + + def get_runner(self, name): + runner = self._get_runner(name) + if runner is None: + self._raise_no_keyword_found(name) + return runner + + def _raise_no_keyword_found(self, name): + msg = "No keyword with name '%s' found." % name + finder = KeywordRecommendationFinder(self.user_keywords, + self.libraries, + self.resources) + recommendations = finder.recommend_similar_keywords(name) + msg = finder.format_recommendations(msg, recommendations) + raise KeywordError(msg) + + def _get_runner(self, name): + if not name: + raise DataError('Keyword name cannot be empty.') + if not is_string(name): + raise DataError('Keyword name must be a string.') + runner = self._get_runner_from_test_case_file(name) + if not runner and '.' in name: + runner = self._get_explicit_runner(name) + if not runner: + runner = self._get_implicit_runner(name) + if not runner: + runner = self._get_bdd_style_runner(name) + return runner + + def _get_bdd_style_runner(self, name): + lower = name.lower() + for prefix in ['given ', 'when ', 'then ', 'and ', 'but ']: + if lower.startswith(prefix): + runner = self._get_runner(name[len(prefix):]) + if runner: + runner = copy.copy(runner) + runner.name = name + return runner + return None + + def _get_implicit_runner(self, name): + runner = self._get_runner_from_resource_files(name) + if not runner: + runner = self._get_runner_from_libraries(name) + return runner + + def _get_runner_from_test_case_file(self, name): + if name in self.user_keywords.handlers: + return self.user_keywords.handlers.create_runner(name) + + def _get_runner_from_resource_files(self, name): + found = [lib.handlers.create_runner(name) + for lib in self.resources.values() + if name in lib.handlers] + if not found: + return None + if len(found) > 1: + found = self._get_runner_based_on_search_order(found) + if len(found) == 1: + return found[0] + self._raise_multiple_keywords_found(name, found) + + def _get_runner_from_libraries(self, name): + found = [lib.handlers.create_runner(name) for lib in self.libraries.values() + if name in lib.handlers] + if not found: + return None + if len(found) > 1: + found = self._get_runner_based_on_search_order(found) + if len(found) == 2: + found = self._filter_stdlib_runner(*found) + if len(found) == 1: + return found[0] + self._raise_multiple_keywords_found(name, found) + + def _get_runner_based_on_search_order(self, runners): + for libname in self.search_order: + for runner in runners: + if eq(libname, runner.libname): + return [runner] + return runners + + def _filter_stdlib_runner(self, runner1, runner2): + stdlibs_without_remote = STDLIBS - {'Remote'} + if runner1.library.orig_name in stdlibs_without_remote: + standard, custom = runner1, runner2 + elif runner2.library.orig_name in stdlibs_without_remote: + standard, custom = runner2, runner1 + else: + return [runner1, runner2] + if not RUN_KW_REGISTER.is_run_keyword(custom.library.orig_name, custom.name): + self._custom_and_standard_keyword_conflict_warning(custom, standard) + return [custom] + + def _custom_and_standard_keyword_conflict_warning(self, custom, standard): + custom_with_name = standard_with_name = '' + if custom.library.name != custom.library.orig_name: + custom_with_name = " imported as '%s'" % custom.library.name + if standard.library.name != standard.library.orig_name: + standard_with_name = " imported as '%s'" % standard.library.name + warning = Message("Keyword '%s' found both from a custom test library " + "'%s'%s and a standard library '%s'%s. The custom " + "keyword is used. To select explicitly, and to get " + "rid of this warning, use either '%s' or '%s'." + % (standard.name, + custom.library.orig_name, custom_with_name, + standard.library.orig_name, standard_with_name, + custom.longname, standard.longname), level='WARN') + if custom.pre_run_messages: + custom.pre_run_messages.append(warning) + else: + custom.pre_run_messages = [warning] + + def _get_explicit_runner(self, name): + found = [] + for owner_name, kw_name in self._yield_owner_and_kw_names(name): + found.extend(self._find_keywords(owner_name, kw_name)) + if len(found) > 1: + self._raise_multiple_keywords_found(name, found, implicit=False) + return found[0] if found else None + + def _yield_owner_and_kw_names(self, full_name): + tokens = full_name.split('.') + for i in range(1, len(tokens)): + yield '.'.join(tokens[:i]), '.'.join(tokens[i:]) + + def _find_keywords(self, owner_name, name): + return [owner.handlers.create_runner(name) + for owner in chain(self.libraries.values(), self.resources.values()) + if eq(owner.name, owner_name) and name in owner.handlers] + + def _raise_multiple_keywords_found(self, name, found, implicit=True): + error = "Multiple keywords with name '%s' found" % name + if implicit: + error += ". Give the full name of the keyword you want to use" + names = sorted(runner.longname for runner in found) + raise KeywordError('\n '.join([error+':'] + names)) + + +class KeywordRecommendationFinder(object): + + def __init__(self, user_keywords, libraries, resources): + self.user_keywords = user_keywords + self.libraries = libraries + self.resources = resources + + def recommend_similar_keywords(self, name): + """Return keyword names similar to `name`.""" + candidates = self._get_candidates('.' in name) + finder = RecommendationFinder( + lambda name: normalize(candidates.get(name, name), ignore='_') + ) + return finder.find(name, candidates) + + @staticmethod + def format_recommendations(message, recommendations): + return RecommendationFinder().format(message, recommendations) + + def _get_candidates(self, use_full_name): + names = {} + for owner, name in self._get_all_handler_names(): + full_name = '%s.%s' % (owner, name) if owner else name + names[full_name] = full_name if use_full_name else name + return names + + def _get_all_handler_names(self): + """Return a list of `(library_name, handler_name)` tuples.""" + handlers = [('', printable_name(handler.name, True)) + for handler in self.user_keywords.handlers] + for library in chain(self.libraries.values(), self.resources.values()): + if library.name != 'Reserved': + handlers.extend( + ((library.name or '', + printable_name(handler.name, code_style=True)) + for handler in library.handlers)) + # sort handlers to ensure consistent ordering between Jython and Python + return sorted(handlers) diff --git a/robot/lib/python3.8/site-packages/robot/running/outputcapture.py b/robot/lib/python3.8/site-packages/robot/running/outputcapture.py new file mode 100644 index 0000000000000000000000000000000000000000..4cc585752b87b3abe491db808d4f856f42fb482d --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/outputcapture.py @@ -0,0 +1,137 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from robot.utils import StringIO + +from robot.output import LOGGER +from robot.utils import console_decode, console_encode, JYTHON + + +class OutputCapturer(object): + + def __init__(self, library_import=False): + self._library_import = library_import + self._python_out = PythonCapturer(stdout=True) + self._python_err = PythonCapturer(stdout=False) + self._java_out = JavaCapturer(stdout=True) + self._java_err = JavaCapturer(stdout=False) + + def __enter__(self): + if self._library_import: + LOGGER.enable_library_import_logging() + return self + + def __exit__(self, exc_type, exc_value, exc_trace): + self._release_and_log() + if self._library_import: + LOGGER.disable_library_import_logging() + return False + + def _release_and_log(self): + stdout, stderr = self._release() + if stdout: + LOGGER.log_output(stdout) + if stderr: + LOGGER.log_output(stderr) + sys.__stderr__.write(console_encode(stderr, stream=sys.__stderr__)) + + def _release(self): + stdout = self._python_out.release() + self._java_out.release() + stderr = self._python_err.release() + self._java_err.release() + return stdout, stderr + + +class PythonCapturer(object): + + def __init__(self, stdout=True): + if stdout: + self._original = sys.stdout + self._set_stream = self._set_stdout + else: + self._original = sys.stderr + self._set_stream = self._set_stderr + self._stream = StringIO() + self._set_stream(self._stream) + + def _set_stdout(self, stream): + sys.stdout = stream + + def _set_stderr(self, stream): + sys.stderr = stream + + def release(self): + # Original stream must be restored before closing the current + self._set_stream(self._original) + try: + return self._get_value(self._stream) + finally: + self._stream.close() + self._avoid_at_exit_errors(self._stream) + + def _get_value(self, stream): + try: + return console_decode(stream.getvalue()) + except UnicodeError: + # Error occurs if non-ASCII chars logged both as str and unicode. + stream.buf = console_decode(stream.buf) + stream.buflist = [console_decode(item) for item in stream.buflist] + return stream.getvalue() + + def _avoid_at_exit_errors(self, stream): + # Avoid ValueError at program exit when logging module tries to call + # methods of streams it has intercepted that are already closed. + # Which methods are called, and does logging silence possible errors, + # depends on Python/Jython version. For related discussion see + # http://bugs.python.org/issue6333 + stream.write = lambda s: None + stream.flush = lambda: None + + +if not JYTHON: + + class JavaCapturer(object): + + def __init__(self, stdout=True): + pass + + def release(self): + return u'' + +else: + + from java.io import ByteArrayOutputStream, PrintStream + from java.lang import System + + class JavaCapturer(object): + + def __init__(self, stdout=True): + if stdout: + self._original = System.out + self._set_stream = System.setOut + else: + self._original = System.err + self._set_stream = System.setErr + self._bytes = ByteArrayOutputStream() + self._stream = PrintStream(self._bytes, False, 'UTF-8') + self._set_stream(self._stream) + + def release(self): + # Original stream must be restored before closing the current + self._set_stream(self._original) + self._stream.close() + output = self._bytes.toString('UTF-8') + self._bytes.reset() + return output diff --git a/robot/lib/python3.8/site-packages/robot/running/randomizer.py b/robot/lib/python3.8/site-packages/robot/running/randomizer.py new file mode 100644 index 0000000000000000000000000000000000000000..372620df7dc14771ff6a09580a2bc13cc4d62424 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/randomizer.py @@ -0,0 +1,53 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from random import Random + +from robot.model import SuiteVisitor + + +class Randomizer(SuiteVisitor): + + def __init__(self, randomize_suites=True, randomize_tests=True, seed=None): + self.randomize_suites = randomize_suites + self.randomize_tests = randomize_tests + self.seed = seed + # Cannot use just Random(seed) due to + # https://ironpython.codeplex.com/workitem/35155 + args = (seed,) if seed is not None else () + self._shuffle = Random(*args).shuffle + + def start_suite(self, suite): + if not self.randomize_suites and not self.randomize_tests: + return False + if self.randomize_suites: + self._shuffle(suite.suites) + if self.randomize_tests: + self._shuffle(suite.tests) + if not suite.parent: + suite.metadata['Randomized'] = self._get_message() + + def _get_message(self): + possibilities = {(True, True): 'Suites and tests', + (True, False): 'Suites', + (False, True): 'Tests'} + randomized = (self.randomize_suites, self.randomize_tests) + return '%s (seed %s)' % (possibilities[randomized], self.seed) + + def visit_test(self, test): + pass + + def visit_keyword(self, kw): + pass diff --git a/robot/lib/python3.8/site-packages/robot/running/runkwregister.py b/robot/lib/python3.8/site-packages/robot/running/runkwregister.py new file mode 100644 index 0000000000000000000000000000000000000000..f45fbb1746213ddcb80e9b998a5fa550bf8d7ef7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/runkwregister.py @@ -0,0 +1,65 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect +import warnings + +from robot.utils import NormalizedDict, PY3 + + +class _RunKeywordRegister(object): + + def __init__(self): + self._libs = {} + + def register_run_keyword(self, libname, keyword, args_to_process=None, + deprecation_warning=True): + if deprecation_warning: + warnings.warn(self._deprecation_warning(), UserWarning) + if args_to_process is None: + args_to_process = self._get_args_from_method(keyword) + keyword = keyword.__name__ + if libname not in self._libs: + self._libs[libname] = NormalizedDict(ignore=['_']) + self._libs[libname][keyword] = int(args_to_process) + + def _deprecation_warning(self): + return ("The API to register run keyword variants and to disable " + "variable resolving in keyword arguments will change in Robot " + "Framework 3.1. For more information see issue #2190 <" + "https://github.com/robotframework/robotframework/issues/2190" + ">. Use with 'deprecation_warning=False' to avoid related " + "deprecation warnings.") + + def get_args_to_process(self, libname, kwname): + if libname in self._libs and kwname in self._libs[libname]: + return self._libs[libname][kwname] + return -1 + + def is_run_keyword(self, libname, kwname): + return self.get_args_to_process(libname, kwname) >= 0 + + def _get_args_from_method(self, method): + if PY3: + raise RuntimeError('Cannot determine arguments to process ' + 'automatically in Python 3.') + if inspect.ismethod(method): + return method.__code__.co_argcount - 1 + elif inspect.isfunction(method): + return method.__code__.co_argcount + raise ValueError('Needs function or method') + + +RUN_KW_REGISTER = _RunKeywordRegister() diff --git a/robot/lib/python3.8/site-packages/robot/running/runner.py b/robot/lib/python3.8/site-packages/robot/running/runner.py new file mode 100644 index 0000000000000000000000000000000000000000..9ef94999b121106d5ded7d39bb929a418faa3eae --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/runner.py @@ -0,0 +1,218 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import ExecutionStatus, DataError, PassExecution +from robot.model import SuiteVisitor +from robot.result import TestSuite, Result +from robot.utils import get_timestamp, is_list_like, NormalizedDict, unic +from robot.variables import VariableScopes + +from .context import EXECUTION_CONTEXTS +from .steprunner import StepRunner +from .namespace import Namespace +from .status import SuiteStatus, TestStatus +from .timeouts import TestTimeout + + +# Some 'extract method' love needed here. Perhaps even 'extract class'. + +class Runner(SuiteVisitor): + + def __init__(self, output, settings): + self.result = None + self._output = output + self._settings = settings + self._variables = VariableScopes(settings) + self._suite = None + self._suite_status = None + self._executed_tests = None + + @property + def _context(self): + return EXECUTION_CONTEXTS.current + + def start_suite(self, suite): + self._output.library_listeners.new_suite_scope() + result = TestSuite(source=suite.source, + name=suite.name, + doc=suite.doc, + metadata=suite.metadata, + starttime=get_timestamp(), + rpa=self._settings.rpa) + if not self.result: + result.set_criticality(self._settings.critical_tags, + self._settings.non_critical_tags) + self.result = Result(root_suite=result, rpa=self._settings.rpa) + self.result.configure(status_rc=self._settings.status_rc, + stat_config=self._settings.statistics_config) + else: + self._suite.suites.append(result) + self._suite = result + self._suite_status = SuiteStatus(self._suite_status, + self._settings.exit_on_failure, + self._settings.exit_on_error, + self._settings.skip_teardown_on_exit) + ns = Namespace(self._variables, result, suite.resource) + ns.start_suite() + ns.variables.set_from_variable_table(suite.resource.variables) + EXECUTION_CONTEXTS.start_suite(result, ns, self._output, + self._settings.dry_run) + self._context.set_suite_variables(result) + if not self._suite_status.failures: + ns.handle_imports() + ns.variables.resolve_delayed() + result.doc = self._resolve_setting(result.doc) + result.metadata = [(self._resolve_setting(n), self._resolve_setting(v)) + for n, v in result.metadata.items()] + self._context.set_suite_variables(result) + self._output.start_suite(ModelCombiner(suite, result, + tests=suite.tests, + suites=suite.suites, + test_count=suite.test_count)) + self._output.register_error_listener(self._suite_status.error_occurred) + self._run_setup(suite.keywords.setup, self._suite_status) + self._executed_tests = NormalizedDict(ignore='_') + + def _resolve_setting(self, value): + if is_list_like(value): + return self._variables.replace_list(value, ignore_errors=True) + return self._variables.replace_string(value, ignore_errors=True) + + def end_suite(self, suite): + self._suite.message = self._suite_status.message + self._context.report_suite_status(self._suite.status, + self._suite.full_message) + with self._context.suite_teardown(): + failure = self._run_teardown(suite.keywords.teardown, self._suite_status) + if failure: + self._suite.suite_teardown_failed(unic(failure)) + if self._suite.statistics.critical.failed: + self._suite_status.critical_failure_occurred() + self._suite.endtime = get_timestamp() + self._suite.message = self._suite_status.message + self._context.end_suite(ModelCombiner(suite, self._suite)) + self._suite = self._suite.parent + self._suite_status = self._suite_status.parent + self._output.library_listeners.discard_suite_scope() + + def visit_test(self, test): + if test.name in self._executed_tests: + self._output.warn("Multiple test cases with name '%s' executed in " + "test suite '%s'." % (test.name, self._suite.longname)) + self._executed_tests[test.name] = True + result = self._suite.tests.create(name=self._resolve_setting(test.name), + doc=self._resolve_setting(test.doc), + tags=self._resolve_setting(test.tags), + starttime=get_timestamp(), + timeout=self._get_timeout(test)) + self._context.start_test(result) + self._output.start_test(ModelCombiner(test, result)) + status = TestStatus(self._suite_status, result) + if status.exit: + self._add_exit_combine() + result.tags.add('robot:exit') + if not status.failures and not test.name: + status.test_failed('Test case name cannot be empty.') + if not status.failures and not test.keywords.normal: + status.test_failed('Test case contains no keywords.') + self._run_setup(test.keywords.setup, status, result) + try: + if not status.failures: + StepRunner(self._context, + test.template).run_steps(test.keywords.normal) + else: + status.test_failed(status.message) + except PassExecution as exception: + err = exception.earlier_failures + if err: + status.test_failed(err) + else: + result.message = exception.message + except ExecutionStatus as err: + status.test_failed(err) + result.status = status.status + result.message = status.message or result.message + if status.teardown_allowed: + with self._context.test_teardown(result): + failure = self._run_teardown(test.keywords.teardown, status, + result) + if failure and result.critical: + status.critical_failure_occurred() + if not status.failures and result.timeout and result.timeout.timed_out(): + status.test_failed(result.timeout.get_message()) + result.message = status.message + result.status = status.status + result.endtime = get_timestamp() + self._output.end_test(ModelCombiner(test, result)) + self._context.end_test(result) + + def _add_exit_combine(self): + exit_combine = ('NOT robot:exit', '') + if exit_combine not in self._settings['TagStatCombine']: + self._settings['TagStatCombine'].append(exit_combine) + + def _get_timeout(self, test): + if not test.timeout: + return None + return TestTimeout(test.timeout, self._variables, rpa=test.parent.rpa) + + def _run_setup(self, setup, status, result=None): + if not status.failures: + exception = self._run_setup_or_teardown(setup) + status.setup_executed(exception) + if result and isinstance(exception, PassExecution): + result.message = exception.message + + def _run_teardown(self, teardown, status, result=None): + if status.teardown_allowed: + exception = self._run_setup_or_teardown(teardown) + status.teardown_executed(exception) + failed = not isinstance(exception, PassExecution) + if result and exception: + result.message = status.message if failed else exception.message + return exception if failed else None + + def _run_setup_or_teardown(self, data): + if not data: + return None + try: + name = self._variables.replace_string(data.name) + except DataError as err: + if self._settings.dry_run: + return None + return err + if name.upper() in ('', 'NONE'): + return None + try: + StepRunner(self._context).run_step(data, name=name) + except ExecutionStatus as err: + return err + + +class ModelCombiner(object): + + def __init__(self, data, result, **priority): + self.data = data + self.result = result + self.priority = priority + + def __getattr__(self, name): + if name in self.priority: + return self.priority[name] + if hasattr(self.result, name): + return getattr(self.result, name) + if hasattr(self.data, name): + return getattr(self.data, name) + raise AttributeError(name) diff --git a/robot/lib/python3.8/site-packages/robot/running/signalhandler.py b/robot/lib/python3.8/site-packages/robot/running/signalhandler.py new file mode 100644 index 0000000000000000000000000000000000000000..f346afa185e526f4b043c1db2caff11e63705e87 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/signalhandler.py @@ -0,0 +1,91 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from threading import currentThread +import signal + +from robot.errors import ExecutionFailed +from robot.output import LOGGER +from robot.utils import JYTHON + +if JYTHON: + from java.lang import IllegalArgumentException +else: + IllegalArgumentException = ValueError + + +class _StopSignalMonitor(object): + + def __init__(self): + self._signal_count = 0 + self._running_keyword = False + self._orig_sigint = None + self._orig_sigterm = None + + def __call__(self, signum, frame): + self._signal_count += 1 + LOGGER.info('Received signal: %s.' % signum) + if self._signal_count > 1: + sys.__stderr__.write('Execution forcefully stopped.\n') + raise SystemExit() + sys.__stderr__.write('Second signal will force exit.\n') + if self._running_keyword and not JYTHON: + self._stop_execution_gracefully() + + def _stop_execution_gracefully(self): + raise ExecutionFailed('Execution terminated by signal', exit=True) + + def __enter__(self): + if self._can_register_signal: + self._orig_sigint = signal.getsignal(signal.SIGINT) + self._orig_sigterm = signal.getsignal(signal.SIGTERM) + for signum in signal.SIGINT, signal.SIGTERM: + self._register_signal_handler(signum) + return self + + def __exit__(self, *exc_info): + if self._can_register_signal: + signal.signal(signal.SIGINT, self._orig_sigint or signal.SIG_DFL) + signal.signal(signal.SIGTERM, self._orig_sigterm or signal.SIG_DFL) + + @property + def _can_register_signal(self): + return signal and currentThread().getName() == 'MainThread' + + def _register_signal_handler(self, signum): + try: + signal.signal(signum, self) + except (ValueError, IllegalArgumentException) as err: + # IllegalArgumentException due to http://bugs.jython.org/issue1729 + self._warn_about_registeration_error(signum, err) + + def _warn_about_registeration_error(self, signum, err): + name, ctrlc = {signal.SIGINT: ('INT', 'or with Ctrl-C '), + signal.SIGTERM: ('TERM', '')}[signum] + LOGGER.warn('Registering signal %s failed. Stopping execution ' + 'gracefully with this signal %sis not possible. ' + 'Original error was: %s' % (name, ctrlc, err)) + + def start_running_keyword(self, in_teardown): + self._running_keyword = True + if self._signal_count and not in_teardown: + self._stop_execution_gracefully() + + def stop_running_keyword(self): + self._running_keyword = False + + +STOP_SIGNAL_MONITOR = _StopSignalMonitor() diff --git a/robot/lib/python3.8/site-packages/robot/running/status.py b/robot/lib/python3.8/site-packages/robot/running/status.py new file mode 100644 index 0000000000000000000000000000000000000000..ae14b59891745353c1f29fdf307d18c7c623c282 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/status.py @@ -0,0 +1,234 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import ExecutionFailed, PassExecution +from robot.utils import html_escape, py2to3, unic + + +@py2to3 +class Failure(object): + + def __init__(self): + self.setup = None + self.test = None + self.teardown = None + + def __nonzero__(self): + return bool(self.setup or self.test or self.teardown) + + +@py2to3 +class Exit(object): + + def __init__(self, failure_mode=False, error_mode=False, + skip_teardown_mode=False): + self.failure_mode = failure_mode + self.error_mode = error_mode + self.skip_teardown_mode = skip_teardown_mode + self.failure = False + self.error = False + self.fatal = False + + def failure_occurred(self, failure=None, critical=False): + if isinstance(failure, ExecutionFailed) and failure.exit: + self.fatal = True + if critical and self.failure_mode: + self.failure = True + + def error_occurred(self): + if self.error_mode: + self.error = True + + @property + def teardown_allowed(self): + return not (self.skip_teardown_mode and self) + + def __nonzero__(self): + return self.failure or self.error or self.fatal + + +class _ExecutionStatus(object): + + def __init__(self, parent=None, *exit_modes): + self.parent = parent + self.children = [] + self.failure = Failure() + self.exit = parent.exit if parent else Exit(*exit_modes) + self._teardown_allowed = False + if parent: + parent.children.append(self) + + def setup_executed(self, failure=None): + if failure and not isinstance(failure, PassExecution): + self.failure.setup = unic(failure) + self.exit.failure_occurred(failure) + self._teardown_allowed = True + + def teardown_executed(self, failure=None): + if failure and not isinstance(failure, PassExecution): + self.failure.teardown = unic(failure) + self.exit.failure_occurred(failure) + + def critical_failure_occurred(self): + self.exit.failure_occurred(critical=True) + + def error_occurred(self): + self.exit.error_occurred() + + @property + def teardown_allowed(self): + return self.exit.teardown_allowed and self._teardown_allowed + + @property + def failures(self): + return bool(self.parent and self.parent.failures or + self.failure or self.exit) + + def _parent_failures(self): + return self.parent and self.parent.failures + + @property + def status(self): + return 'FAIL' if self.failures else 'PASS' + + @property + def message(self): + if self.failure or self.exit: + return self._my_message() + if self.parent and self.parent.failures: + return self._parent_message() + return '' + + def _my_message(self): + raise NotImplementedError + + def _parent_message(self): + return ParentMessage(self.parent).message + + +class SuiteStatus(_ExecutionStatus): + + def __init__(self, parent=None, exit_on_failure_mode=False, + exit_on_error_mode=False, + skip_teardown_on_exit_mode=False): + _ExecutionStatus.__init__(self, parent, exit_on_failure_mode, + exit_on_error_mode, + skip_teardown_on_exit_mode) + + def _my_message(self): + return SuiteMessage(self).message + + +class TestStatus(_ExecutionStatus): + + def __init__(self, parent, test): + _ExecutionStatus.__init__(self, parent) + self.exit = parent.exit + self._test = test + + def test_failed(self, failure): + self.failure.test = unic(failure) + self.exit.failure_occurred(failure, self._test.critical) + + def _my_message(self): + return TestMessage(self).message + + +class _Message(object): + setup_message = NotImplemented + teardown_message = NotImplemented + also_teardown_message = NotImplemented + + def __init__(self, status): + self.failure = status.failure + + @property + def message(self): + message = self._get_message_before_teardown() + return self._get_message_after_teardown(message) + + def _get_message_before_teardown(self): + if self.failure.setup: + return self._format_setup_or_teardown_message(self.setup_message, + self.failure.setup) + return self.failure.test or '' + + def _format_setup_or_teardown_message(self, prefix, message): + if message.startswith('*HTML*'): + prefix = '*HTML* ' + prefix + message = message[6:].lstrip() + return prefix % message + + def _get_message_after_teardown(self, message): + if not self.failure.teardown: + return message + if not message: + return self._format_setup_or_teardown_message(self.teardown_message, + self.failure.teardown) + return self._format_message_with_teardown_message(message) + + def _format_message_with_teardown_message(self, message): + teardown = self.failure.teardown + if teardown.startswith('*HTML*'): + teardown = teardown[6:].lstrip() + if not message.startswith('*HTML*'): + message = '*HTML* ' + html_escape(message) + elif message.startswith('*HTML*'): + teardown = html_escape(teardown) + return self.also_teardown_message % (message, teardown) + + +class TestMessage(_Message): + setup_message = 'Setup failed:\n%s' + teardown_message = 'Teardown failed:\n%s' + also_teardown_message = '%s\n\nAlso teardown failed:\n%s' + exit_on_fatal_message = 'Test execution stopped due to a fatal error.' + exit_on_failure_message = \ + 'Critical failure occurred and exit-on-failure mode is in use.' + exit_on_error_message = 'Error occurred and exit-on-error mode is in use.' + + def __init__(self, status): + _Message.__init__(self, status) + self.exit = status.exit + + @property + def message(self): + message = super(TestMessage, self).message + if message: + return message + if self.exit.failure: + return self.exit_on_failure_message + if self.exit.fatal: + return self.exit_on_fatal_message + if self.exit.error: + return self.exit_on_error_message + return '' + + +class SuiteMessage(_Message): + setup_message = 'Suite setup failed:\n%s' + teardown_message = 'Suite teardown failed:\n%s' + also_teardown_message = '%s\n\nAlso suite teardown failed:\n%s' + + +class ParentMessage(SuiteMessage): + setup_message = 'Parent suite setup failed:\n%s' + teardown_message = 'Parent suite teardown failed:\n%s' + also_teardown_message = '%s\n\nAlso parent suite teardown failed:\n%s' + + def __init__(self, status): + while status.parent and status.parent.failures: + status = status.parent + SuiteMessage.__init__(self, status) diff --git a/robot/lib/python3.8/site-packages/robot/running/statusreporter.py b/robot/lib/python3.8/site-packages/robot/running/statusreporter.py new file mode 100644 index 0000000000000000000000000000000000000000..64d1b4ee6bf80c44c1ce0ee2ab7dab51a0697f91 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/statusreporter.py @@ -0,0 +1,75 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import (ExecutionFailed, ExecutionStatus, DataError, + HandlerExecutionFailed, KeywordError, VariableError) +from robot.utils import ErrorDetails, get_timestamp + + +class StatusReporter(object): + + def __init__(self, context, result, dry_run_lib_kw=False): + self._context = context + self._result = result + self._pass_status = 'PASS' if not dry_run_lib_kw else 'NOT_RUN' + self._test_passed = None + + def __enter__(self): + if self._context.test: + self._test_passed = self._context.test.passed + self._result.starttime = get_timestamp() + self._context.start_keyword(self._result) + self._warn_if_deprecated(self._result.doc, self._result.name) + + def _warn_if_deprecated(self, doc, name): + if doc.startswith('*DEPRECATED') and '*' in doc[1:]: + message = ' ' + doc.split('*', 2)[-1].strip() + self._context.warn("Keyword '%s' is deprecated.%s" % (name, message)) + + def __exit__(self, exc_type, exc_val, exc_tb): + context = self._context + result = self._result + failure = self._get_failure(exc_type, exc_val, exc_tb, context) + if failure is None: + result.status = self._pass_status + else: + result.status = failure.status + if result.type == result.TEARDOWN_TYPE: + result.message = failure.message + if context.test: + context.test.passed = self._test_passed and result.passed + result.endtime = get_timestamp() + context.end_keyword(result) + if failure is not exc_val: + raise failure + + def _get_failure(self, exc_type, exc_value, exc_tb, context): + if exc_value is None: + return None + if isinstance(exc_value, ExecutionStatus): + return exc_value + if isinstance(exc_value, DataError): + msg = exc_value.message + context.fail(msg) + syntax = not isinstance(exc_value, (KeywordError, VariableError)) + return ExecutionFailed(msg, syntax=syntax) + exc_info = (exc_type, exc_value, exc_tb) + failure = HandlerExecutionFailed(ErrorDetails(exc_info)) + if failure.timeout: + context.timeout_occurred = True + context.fail(failure.full_message) + if failure.traceback: + context.debug(failure.traceback) + return failure diff --git a/robot/lib/python3.8/site-packages/robot/running/steprunner.py b/robot/lib/python3.8/site-packages/robot/running/steprunner.py new file mode 100644 index 0000000000000000000000000000000000000000..8521ad45777dc88d5410d3db6d96a647baf01cbe --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/steprunner.py @@ -0,0 +1,311 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import OrderedDict + +from robot.errors import (ExecutionFailed, ExecutionFailures, ExecutionPassed, + ExitForLoop, ContinueForLoop, DataError) +from robot.result import Keyword as KeywordResult +from robot.output import librarylogger as logger +from robot.utils import (format_assign_message, frange, get_error_message, + is_list_like, is_number, plural_or_not as s, + split_from_equals, type_name) +from robot.variables import is_dict_variable, is_scalar_assign + +from .statusreporter import StatusReporter + + +class StepRunner(object): + + def __init__(self, context, templated=False): + self._context = context + self._templated = bool(templated) + + def run_steps(self, steps): + errors = [] + for step in steps: + try: + self.run_step(step) + except ExecutionPassed as exception: + exception.set_earlier_failures(errors) + raise exception + except ExecutionFailed as exception: + errors.extend(exception.get_errors()) + if not exception.can_continue(self._context.in_teardown, + self._templated, + self._context.dry_run): + break + if errors: + raise ExecutionFailures(errors) + + def run_step(self, step, name=None): + context = self._context + if step.type == step.FOR_LOOP_TYPE: + runner = ForRunner(context, self._templated, step.flavor) + return runner.run(step) + runner = context.get_runner(name or step.name) + if context.dry_run: + return runner.dry_run(step, context) + return runner.run(step, context) + + +def ForRunner(context, templated=False, flavor='IN'): + runners = {'IN': ForInRunner, + 'IN RANGE': ForInRangeRunner, + 'IN ZIP': ForInZipRunner, + 'IN ENUMERATE': ForInEnumerateRunner} + runner = runners[flavor or 'IN'] + return runner(context, templated) + + +class ForInRunner(object): + flavor = 'IN' + + def __init__(self, context, templated=False): + self._context = context + self._templated = templated + + def run(self, data, name=None): + result = KeywordResult(kwname=self._get_name(data), + type=data.FOR_LOOP_TYPE) + with StatusReporter(self._context, result): + self._validate(data) + self._run(data) + + def _get_name(self, data): + return '%s %s [ %s ]' % (' | '.join(data.variables), + self.flavor, + ' | '.join(data.values)) + + def _validate(self, data): + # TODO: Remove header and end deprecations in RF 3.3! + if data._header != 'FOR': + self._deprecated("For loop header '%s' is deprecated. " + "Use 'FOR' instead." % data._header, data) + if data._end is None: + raise DataError("For loop has no closing 'END'.") + if data._end != 'END': + self._deprecated("Marking for loop body with '\\' is deprecated. " + "Remove markers and use 'END' instead.", data) + if not data.variables: + raise DataError('FOR loop has no loop variables.') + for var in data.variables: + if not is_scalar_assign(var): + raise DataError("Invalid FOR loop variable '%s'." % var) + if not data.values: + raise DataError('FOR loop has no loop values.') + if not data.keywords: + raise DataError('FOR loop contains no keywords.') + + def _deprecated(self, message, data): + logger.warn("Error in file '%s' in FOR loop starting on line %s: %s" + % (data.source, data.lineno, message)) + + def _run(self, data): + errors = [] + for values in self._get_values_for_rounds(data): + try: + self._run_one_round(data, values) + except ExitForLoop as exception: + if exception.earlier_failures: + errors.extend(exception.earlier_failures.get_errors()) + break + except ContinueForLoop as exception: + if exception.earlier_failures: + errors.extend(exception.earlier_failures.get_errors()) + continue + except ExecutionPassed as exception: + exception.set_earlier_failures(errors) + raise exception + except ExecutionFailed as exception: + errors.extend(exception.get_errors()) + if not exception.can_continue(self._context.in_teardown, + self._templated, + self._context.dry_run): + break + if errors: + raise ExecutionFailures(errors) + + def _get_values_for_rounds(self, data): + values_per_round = len(data.variables) + if self._context.dry_run: + return ForInRunner._map_values_to_rounds( + self, data.variables, values_per_round + ) + if self._is_dict_iteration(data.values): + values = self._resolve_dict_values(data.values) + values = self._map_dict_values_to_rounds(values, values_per_round) + else: + values = self._context.variables.replace_list(data.values) + values = self._map_values_to_rounds(values, values_per_round) + return values + + def _is_dict_iteration(self, values): + all_name_value = True + for item in values: + if is_dict_variable(item): + return True + if split_from_equals(item)[1] is None: + all_name_value = False + if all_name_value: + name, value = split_from_equals(values[0]) + logger.warn( + "FOR loop iteration over values that are all in 'name=value' " + "format like '%s' is deprecated. In the future this syntax " + "will mean iterating over names and values separately like " + "when iterating over '&{dict} variables. Escape at least one " + "of the values like '%s\\=%s' to use normal FOR loop " + "iteration and to disable this warning." + % (values[0], name, value) + ) + return False + + def _resolve_dict_values(self, values): + result = OrderedDict() + replace_scalar = self._context.variables.replace_scalar + for item in values: + if is_dict_variable(item): + result.update(replace_scalar(item)) + else: + key, value = split_from_equals(item) + if value is None: + raise DataError( + "Invalid FOR loop value '%s'. When iterating over " + "dictionaries, values must be '&{dict}' variables " + "or use 'key=value' syntax." % item + ) + try: + result[replace_scalar(key)] = replace_scalar(value) + except TypeError: + raise DataError( + "Invalid dictionary item '%s': %s" + % (item, get_error_message()) + ) + return result.items() + + def _map_dict_values_to_rounds(self, values, per_round): + if per_round > 2: + raise DataError( + 'Number of FOR loop variables must be 1 or 2 when iterating ' + 'over dictionaries, got %d.' % per_round + ) + return values + + def _map_values_to_rounds(self, values, per_round): + count = len(values) + if count % per_round != 0: + self._raise_wrong_variable_count(per_round, count) + # Map list of values to list of lists containing values per round. + return (values[i:i+per_round] for i in range(0, count, per_round)) + + def _raise_wrong_variable_count(self, variables, values): + raise DataError( + 'Number of FOR loop values should be multiple of its variables. ' + 'Got %d variables but %d value%s.' % (variables, values, s(values)) + ) + + def _run_one_round(self, data, values): + variables = self._map_variables_and_values(data.variables, values) + for name, value in variables: + self._context.variables[name] = value + name = ', '.join(format_assign_message(n, v) for n, v in variables) + result = KeywordResult(kwname=name, + type=data.FOR_ITEM_TYPE) + runner = StepRunner(self._context, self._templated) + with StatusReporter(self._context, result): + runner.run_steps(data.keywords) + + def _map_variables_and_values(self, variables, values): + if len(variables) == 1 and len(values) != 1: + return [(variables[0], tuple(values))] + return list(zip(variables, values)) + + +class ForInRangeRunner(ForInRunner): + flavor = 'IN RANGE' + + def _resolve_dict_values(self, values): + raise DataError( + 'FOR IN RANGE loops do not support iterating over dictionaries.' + ) + + def _map_values_to_rounds(self, values, per_round): + if not 1 <= len(values) <= 3: + raise DataError( + 'FOR IN RANGE expected 1-3 values, got %d.' % len(values) + ) + try: + values = [self._to_number_with_arithmetic(v) for v in values] + except: + raise DataError( + 'Converting FOR IN RANGE values failed: %s.' + % get_error_message() + ) + values = frange(*values) + return ForInRunner._map_values_to_rounds(self, values, per_round) + + def _to_number_with_arithmetic(self, item): + if is_number(item): + return item + number = eval(str(item), {}) + if not is_number(number): + raise TypeError("Expected number, got %s." % type_name(item)) + return number + + +class ForInZipRunner(ForInRunner): + flavor = 'IN ZIP' + + def _resolve_dict_values(self, values): + raise DataError( + 'FOR IN ZIP loops do not support iterating over dictionaries.' + ) + + def _map_values_to_rounds(self, values, per_round): + for item in values: + if not is_list_like(item): + raise DataError( + "FOR IN ZIP items must all be list-like, got %s '%s'." + % (type_name(item), item) + ) + if len(values) % per_round != 0: + self._raise_wrong_variable_count(per_round, len(values)) + return zip(*(list(item) for item in values)) + + +class ForInEnumerateRunner(ForInRunner): + flavor = 'IN ENUMERATE' + + def _map_dict_values_to_rounds(self, values, per_round): + if per_round > 3: + raise DataError( + 'Number of FOR IN ENUMERATE loop variables must be 1-3 when ' + 'iterating over dictionaries, got %d.' % per_round + ) + if per_round == 2: + return ((index, item) for index, item in enumerate(values)) + return ((index,) + item for index, item in enumerate(values)) + + def _map_values_to_rounds(self, values, per_round): + per_round = max(per_round-1, 1) + values = ForInRunner._map_values_to_rounds(self, values, per_round) + return ([i] + v for i, v in enumerate(values)) + + def _raise_wrong_variable_count(self, variables, values): + raise DataError( + 'Number of FOR IN ENUMERATE loop values should be multiple of ' + 'its variables (excluding the index). Got %d variables but %d ' + 'value%s.' % (variables, values, s(values)) + ) diff --git a/robot/lib/python3.8/site-packages/robot/running/testlibraries.py b/robot/lib/python3.8/site-packages/robot/running/testlibraries.py new file mode 100644 index 0000000000000000000000000000000000000000..693cd95a2968598d9b90e6153177e3b83acf3349 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/testlibraries.py @@ -0,0 +1,438 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect +import os + +from robot.errors import DataError +from robot.libraries import STDLIBS +from robot.output import LOGGER +from robot.utils import (getdoc, get_error_details, Importer, is_java_init, + is_java_method, JYTHON, normalize, seq2str2, unic, + is_list_like, PY2, PYPY, type_name) + +from .arguments import EmbeddedArguments +from .context import EXECUTION_CONTEXTS +from .dynamicmethods import (GetKeywordArguments, GetKeywordDocumentation, + GetKeywordNames, GetKeywordTags, RunKeyword) +from .handlers import Handler, InitHandler, DynamicHandler, EmbeddedArgumentsHandler +from .handlerstore import HandlerStore +from .libraryscopes import LibraryScope +from .outputcapture import OutputCapturer + + +if JYTHON: + from java.lang import Object +else: + Object = None + + +def TestLibrary(name, args=None, variables=None, create_handlers=True, + logger=LOGGER): + if name in STDLIBS: + import_name = 'robot.libraries.' + name + else: + import_name = name + with OutputCapturer(library_import=True): + importer = Importer('test library') + libcode, source = importer.import_class_or_module(import_name, + return_source=True) + libclass = _get_lib_class(libcode) + lib = libclass(libcode, name, args or [], source, logger, variables) + if create_handlers: + lib.create_handlers() + return lib + + +def _get_lib_class(libcode): + if inspect.ismodule(libcode): + return _ModuleLibrary + if GetKeywordNames(libcode): + if RunKeyword(libcode): + return _DynamicLibrary + else: + return _HybridLibrary + return _ClassLibrary + + +class _BaseTestLibrary(object): + get_handler_error_level = 'INFO' + + def __init__(self, libcode, name, args, source, logger, variables): + if os.path.exists(name): + name = os.path.splitext(os.path.basename(os.path.abspath(name)))[0] + self.version = self._get_version(libcode) + self.name = name + self.orig_name = name # Stores original name when importing WITH NAME + self.source = source + self.logger = logger + self.handlers = HandlerStore(self.name, HandlerStore.TEST_LIBRARY_TYPE) + self.has_listener = None # Set when first instance is created + self._doc = None + self.doc_format = self._get_doc_format(libcode) + self.scope = LibraryScope(libcode, self) + self.init = self._create_init_handler(libcode) + self.positional_args, self.named_args \ + = self.init.resolve_arguments(args, variables) + self._libcode = libcode + self._libinst = None + + def __len__(self): + return len(self.handlers) + + @property + def doc(self): + if self._doc is None: + self._doc = getdoc(self.get_instance()) + return self._doc + + @property + def lineno(self): + if inspect.ismodule(self._libcode): + return 1 + try: + lines, start_lineno = inspect.getsourcelines(self._libcode) + except (TypeError, OSError, IOError): + return -1 + for increment, line in enumerate(lines): + if line.strip().startswith('class '): + return start_lineno + increment + return start_lineno + + def create_handlers(self): + self._create_handlers(self.get_instance()) + self.reset_instance() + + def reload(self): + self.handlers = HandlerStore(self.name, HandlerStore.TEST_LIBRARY_TYPE) + self._create_handlers(self.get_instance()) + + def start_suite(self): + self.scope.start_suite() + + def end_suite(self): + self.scope.end_suite() + + def start_test(self): + self.scope.start_test() + + def end_test(self): + self.scope.end_test() + + def report_error(self, message, details=None, level='ERROR', + details_level='INFO'): + prefix = 'Error in' if level in ('ERROR', 'WARN') else 'In' + self.logger.write("%s library '%s': %s" % (prefix, self.name, message), + level) + if details: + self.logger.write('Details:\n%s' % details, details_level) + + def _get_version(self, libcode): + return self._get_attr(libcode, 'ROBOT_LIBRARY_VERSION') \ + or self._get_attr(libcode, '__version__') + + def _get_attr(self, object, attr, default='', upper=False): + value = unic(getattr(object, attr, default)) + if upper: + value = normalize(value, ignore='_').upper() + return value + + def _get_doc_format(self, libcode): + return self._get_attr(libcode, 'ROBOT_LIBRARY_DOC_FORMAT', upper=True) + + def _create_init_handler(self, libcode): + return InitHandler(self, self._resolve_init_method(libcode)) + + def _resolve_init_method(self, libcode): + init = getattr(libcode, '__init__', None) + return init if self._valid_init(init) else None + + def _valid_init(self, method): + if not method: + return False + # https://bitbucket.org/pypy/pypy/issues/2462/ + if PYPY: + if PY2: + return method.__func__ is not object.__init__.__func__ + return method is not object.__init__ + return (inspect.ismethod(method) or # PY2 + inspect.isfunction(method) or # PY3 + is_java_init(method)) + + def reset_instance(self, instance=None): + prev = self._libinst + if not self.scope.is_global: + self._libinst = instance + return prev + + def get_instance(self, create=True): + if not create: + return self._libinst + if self._libinst is None: + self._libinst = self._get_instance(self._libcode) + if self.has_listener is None: + self.has_listener = bool(self.get_listeners(self._libinst)) + return self._libinst + + def _get_instance(self, libcode): + with OutputCapturer(library_import=True): + try: + return libcode(*self.positional_args, **dict(self.named_args)) + except: + self._raise_creating_instance_failed() + + def get_listeners(self, libinst=None): + if libinst is None: + libinst = self.get_instance() + listeners = getattr(libinst, 'ROBOT_LIBRARY_LISTENER', None) + if listeners is None: + return [] + if is_list_like(listeners): + return listeners + return [listeners] + + def register_listeners(self): + if self.has_listener: + try: + listeners = EXECUTION_CONTEXTS.current.output.library_listeners + listeners.register(self.get_listeners(), self) + except DataError as err: + self.has_listener = False + # Error should have information about suite where the + # problem occurred but we don't have such info here. + self.report_error("Registering listeners failed: %s" % err) + + def unregister_listeners(self, close=False): + if self.has_listener: + listeners = EXECUTION_CONTEXTS.current.output.library_listeners + listeners.unregister(self, close) + + def close_global_listeners(self): + if self.scope.is_global: + for listener in self.get_listeners(): + self._close_listener(listener) + + def _close_listener(self, listener): + method = (getattr(listener, 'close', None) or + getattr(listener, '_close', None)) + try: + if method: + method() + except: + message, details = get_error_details() + name = getattr(listener, '__name__', None) or type_name(listener) + self.report_error("Calling method '%s' of listener '%s' failed: %s" + % (method.__name__, name, message), details) + + def _create_handlers(self, libcode): + try: + names = self._get_handler_names(libcode) + except: + message, details = get_error_details() + raise DataError("Getting keyword names from library '%s' failed: %s" + % (self.name, message), details) + for name in names: + method = self._try_to_get_handler_method(libcode, name) + if method: + handler, embedded = self._try_to_create_handler(name, method) + if handler: + try: + self.handlers.add(handler, embedded) + except DataError as err: + self._adding_keyword_failed(handler.name, err) + else: + self.logger.debug("Created keyword '%s'" % handler.name) + + def _get_handler_names(self, libcode): + def has_robot_name(name): + try: + handler = self._get_handler_method(libcode, name) + except DataError: + return False + return hasattr(handler, 'robot_name') + + auto_keywords = getattr(libcode, 'ROBOT_AUTO_KEYWORDS', True) + if auto_keywords: + predicate = lambda name: name[:1] != '_' or has_robot_name(name) + else: + predicate = has_robot_name + return [name for name in dir(libcode) if predicate(name)] + + def _try_to_get_handler_method(self, libcode, name): + try: + return self._get_handler_method(libcode, name) + except DataError as err: + self._adding_keyword_failed(name, err, self.get_handler_error_level) + return None + + def _adding_keyword_failed(self, name, error, level='ERROR'): + self.report_error( + "Adding keyword '%s' failed: %s" % (name, error.message), + error.details, + level=level, + details_level='DEBUG' + ) + + def _get_handler_method(self, libcode, name): + try: + method = getattr(libcode, name) + except: + message, details = get_error_details() + raise DataError('Getting handler method failed: %s' % message, + details) + self._validate_handler_method(method) + return method + + def _validate_handler_method(self, method): + if not inspect.isroutine(method): + raise DataError('Not a method or function.') + if getattr(method, 'robot_not_keyword', False) is True: + raise DataError('Not exposed as a keyword.') + return method + + def _try_to_create_handler(self, name, method): + try: + handler = self._create_handler(name, method) + except DataError as err: + self._adding_keyword_failed(name, err) + return None, False + try: + return self._get_possible_embedded_args_handler(handler) + except DataError as err: + self._adding_keyword_failed(handler.name, err) + return None, False + + def _create_handler(self, handler_name, handler_method): + return Handler(self, handler_name, handler_method) + + def _get_possible_embedded_args_handler(self, handler): + embedded = EmbeddedArguments(handler.name) + if embedded: + self._validate_embedded_count(embedded, handler.arguments) + return EmbeddedArgumentsHandler(embedded.name, handler), True + return handler, False + + def _validate_embedded_count(self, embedded, arguments): + if not (arguments.minargs <= len(embedded.args) <= arguments.maxargs): + raise DataError('Embedded argument count does not match number of ' + 'accepted arguments.') + + def _raise_creating_instance_failed(self): + msg, details = get_error_details() + if self.positional_args or self.named_args: + args = self.positional_args \ + + ['%s=%s' % item for item in self.named_args] + args_text = 'arguments %s' % seq2str2(args) + else: + args_text = 'no arguments' + raise DataError("Initializing test library '%s' with %s failed: %s\n%s" + % (self.name, args_text, msg, details)) + + +class _ClassLibrary(_BaseTestLibrary): + + def _get_handler_method(self, libinst, name): + # Type is checked before using getattr to avoid calling properties, + # most importantly bean properties generated by Jython (issue 188). + for item in (libinst,) + inspect.getmro(libinst.__class__): + if item in (object, Object): + continue + if hasattr(item, '__dict__') and name in item.__dict__: + self._validate_handler_method(item.__dict__[name]) + return getattr(libinst, name) + raise DataError('No non-implicit implementation found.') + + def _validate_handler_method(self, method): + _BaseTestLibrary._validate_handler_method(self, method) + if self._is_implicit_java_or_jython_method(method): + raise DataError('Implicit methods are ignored.') + + def _is_implicit_java_or_jython_method(self, handler): + if not is_java_method(handler): + return False + for signature in handler.argslist[:handler.nargs]: + cls = signature.declaringClass + if not (cls is Object or cls.__module__ == 'org.python.proxies'): + return False + return True + + +class _ModuleLibrary(_BaseTestLibrary): + + def _get_handler_method(self, libcode, name): + method = _BaseTestLibrary._get_handler_method(self, libcode, name) + if hasattr(libcode, '__all__') and name not in libcode.__all__: + raise DataError('Not exposed as a keyword.') + return method + + def get_instance(self, create=True): + if not create: + return self._libcode + if self.has_listener is None: + self.has_listener = bool(self.get_listeners(self._libcode)) + return self._libcode + + def _create_init_handler(self, libcode): + return InitHandler(self) + + +class _HybridLibrary(_BaseTestLibrary): + get_handler_error_level = 'ERROR' + + def _get_handler_names(self, instance): + return GetKeywordNames(instance)() + + +class _DynamicLibrary(_BaseTestLibrary): + get_handler_error_level = 'ERROR' + + def __init__(self, libcode, name, args, source, logger, variables=None): + _BaseTestLibrary.__init__(self, libcode, name, args, source, logger, + variables) + + @property + def doc(self): + if self._doc is None: + self._doc = (self._get_kw_doc('__intro__') or + _BaseTestLibrary.doc.fget(self)) + return self._doc + + def _get_kw_doc(self, name): + getter = GetKeywordDocumentation(self.get_instance()) + return getter(name) + + def _get_kw_args(self, name): + getter = GetKeywordArguments(self.get_instance()) + return getter(name) + + def _get_kw_tags(self, name): + getter = GetKeywordTags(self.get_instance()) + return getter(name) + + def _get_handler_names(self, instance): + return GetKeywordNames(instance)() + + def _get_handler_method(self, instance, name): + return RunKeyword(instance) + + def _create_handler(self, name, method): + argspec = self._get_kw_args(name) + tags = self._get_kw_tags(name) + doc = self._get_kw_doc(name) + return DynamicHandler(self, name, method, doc, argspec, tags) + + def _create_init_handler(self, libcode): + docgetter = lambda: self._get_kw_doc('__init__') + return InitHandler(self, self._resolve_init_method(libcode), docgetter) diff --git a/robot/lib/python3.8/site-packages/robot/running/timeouts/__init__.py b/robot/lib/python3.8/site-packages/robot/running/timeouts/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a7220438179b3dc59ae1629ea7a3cd24a79ad80c --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/timeouts/__init__.py @@ -0,0 +1,134 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time + +from robot.utils import (Sortable, py2to3, secs_to_timestr, timestr_to_secs, + IRONPYTHON, JYTHON, WINDOWS) +from robot.errors import TimeoutError, DataError, FrameworkError + +if JYTHON: + from .jython import Timeout +elif IRONPYTHON: + from .ironpython import Timeout +elif WINDOWS: + from .windows import Timeout +else: + from .posix import Timeout + + +@py2to3 +class _Timeout(Sortable): + + def __init__(self, timeout=None, variables=None): + self.string = timeout or '' + self.secs = -1 + self.starttime = -1 + self.error = None + if variables: + self.replace_variables(variables) + + @property + def active(self): + return self.starttime > 0 + + def replace_variables(self, variables): + try: + self.string = variables.replace_string(self.string) + if not self: + return + self.secs = timestr_to_secs(self.string) + self.string = secs_to_timestr(self.secs) + except (DataError, ValueError) as err: + self.secs = 0.000001 # to make timeout active + self.error = (u'Setting %s timeout failed: %s' + % (self.type.lower(), err)) + + def start(self): + if self.secs > 0: + self.starttime = time.time() + + def time_left(self): + if not self.active: + return -1 + elapsed = time.time() - self.starttime + # Timeout granularity is 1ms. Without rounding some timeout tests fail + # intermittently on Windows, probably due to threading.Event.wait(). + return round(self.secs - elapsed, 3) + + def timed_out(self): + return self.active and self.time_left() <= 0 + + def run(self, runnable, args=None, kwargs=None): + if self.error: + raise DataError(self.error) + if not self.active: + raise FrameworkError('Timeout is not active') + timeout = self.time_left() + error = TimeoutError(self._timeout_error, + test_timeout=isinstance(self, TestTimeout)) + if timeout <= 0: + raise error + executable = lambda: runnable(*(args or ()), **(kwargs or {})) + return Timeout(timeout, error).execute(executable) + + def get_message(self): + if not self.active: + return '%s timeout not active.' % self.type + if not self.timed_out(): + return '%s timeout %s active. %s seconds left.' \ + % (self.type, self.string, self.time_left()) + return self._timeout_error + + @property + def _timeout_error(self): + return '%s timeout %s exceeded.' % (self.type, self.string) + + def __unicode__(self): + return self.string + + def __nonzero__(self): + return bool(self.string and self.string.upper() != 'NONE') + + @property + def _sort_key(self): + return not self.active, self.time_left() + + def __eq__(self, other): + return self is other + + def __hash__(self): + return id(self) + + +class TestTimeout(_Timeout): + type = 'Test' + _keyword_timeout_occurred = False + + def __init__(self, timeout=None, variables=None, rpa=False): + if rpa: + self.type = 'Task' + _Timeout.__init__(self, timeout, variables) + + def set_keyword_timeout(self, timeout_occurred): + if timeout_occurred: + self._keyword_timeout_occurred = True + + def any_timeout_occurred(self): + return self.timed_out() or self._keyword_timeout_occurred + + +class KeywordTimeout(_Timeout): + type = 'Keyword' diff --git a/robot/lib/python3.8/site-packages/robot/running/timeouts/ironpython.py b/robot/lib/python3.8/site-packages/robot/running/timeouts/ironpython.py new file mode 100644 index 0000000000000000000000000000000000000000..faed0c0d4c8c46ee8efdf457503c5154fbdde745 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/timeouts/ironpython.py @@ -0,0 +1,58 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import threading + +from System.Threading import Thread, ThreadStart + + +class Timeout(object): + + def __init__(self, timeout, error): + self._timeout = timeout + self._error = error + + def execute(self, runnable): + runner = Runner(runnable) + thread = Thread(ThreadStart(runner)) + thread.IsBackground = True + thread.Start() + if not thread.Join(self._timeout * 1000): + thread.Abort() + raise self._error + return runner.get_result() + + +class Runner(object): + + def __init__(self, runnable): + self._runnable = runnable + self._result = None + self._error = None + + def __call__(self): + threading.currentThread().setName('RobotFrameworkTimeoutThread') + try: + self._result = self._runnable() + except: + self._error = sys.exc_info() + + def get_result(self): + if not self._error: + return self._result + # `exec` used to avoid errors with easy_install on Python 3: + # https://github.com/robotframework/robotframework/issues/2785 + exec('raise self._error[0], self._error[1], self._error[2]') diff --git a/robot/lib/python3.8/site-packages/robot/running/timeouts/jython.py b/robot/lib/python3.8/site-packages/robot/running/timeouts/jython.py new file mode 100644 index 0000000000000000000000000000000000000000..52e32a0a85e5c77cdb531fc405835636f76a2e17 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/timeouts/jython.py @@ -0,0 +1,57 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from java.lang import Thread, Runnable + + +class Timeout(object): + + def __init__(self, timeout, error): + self._timeout = timeout + self._error = error + + def execute(self, runnable): + runner = Runner(runnable) + thread = Thread(runner, name='RobotFrameworkTimeoutThread') + thread.setDaemon(True) + thread.start() + thread.join(int(self._timeout * 1000)) + if thread.isAlive(): + thread.stop() + raise self._error + return runner.get_result() + + +class Runner(Runnable): + + def __init__(self, runnable): + self._runnable = runnable + self._result = None + self._error = None + + def run(self): + try: + self._result = self._runnable() + except: + self._error = sys.exc_info() + + def get_result(self): + if not self._error: + return self._result + # `exec` used to avoid errors with easy_install on Python 3: + # https://github.com/robotframework/robotframework/issues/2785 + exec('raise self._error[0], self._error[1], self._error[2]') diff --git a/robot/lib/python3.8/site-packages/robot/running/timeouts/posix.py b/robot/lib/python3.8/site-packages/robot/running/timeouts/posix.py new file mode 100644 index 0000000000000000000000000000000000000000..2017e8bd9f4ad7553a2c85428a4d1e08fe7aa81b --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/timeouts/posix.py @@ -0,0 +1,40 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from signal import setitimer, signal, SIGALRM, ITIMER_REAL + + +class Timeout(object): + + def __init__(self, timeout, error): + self._timeout = timeout + self._error = error + + def execute(self, runnable): + self._start_timer() + try: + return runnable() + finally: + self._stop_timer() + + def _start_timer(self): + signal(SIGALRM, self._raise_timeout_error) + setitimer(ITIMER_REAL, self._timeout) + + def _raise_timeout_error(self, signum, frame): + raise self._error + + def _stop_timer(self): + setitimer(ITIMER_REAL, 0) diff --git a/robot/lib/python3.8/site-packages/robot/running/timeouts/windows.py b/robot/lib/python3.8/site-packages/robot/running/timeouts/windows.py new file mode 100644 index 0000000000000000000000000000000000000000..008b0e3da3530400a24c0f35a7dae550f4461af0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/timeouts/windows.py @@ -0,0 +1,71 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import ctypes +import time +from threading import current_thread, Lock, Timer + + +class Timeout(object): + + def __init__(self, timeout, error): + self._runner_thread_id = current_thread().ident + self._timer = Timer(timeout, self._timed_out) + self._error = error + self._timeout_occurred = False + self._finished = False + self._lock = Lock() + + def execute(self, runnable): + try: + self._start_timer() + try: + result = runnable() + finally: + self._cancel_timer() + self._wait_for_raised_timeout() + return result + finally: + if self._timeout_occurred: + raise self._error + + def _start_timer(self): + self._timer.start() + + def _cancel_timer(self): + with self._lock: + self._finished = True + self._timer.cancel() + + def _wait_for_raised_timeout(self): + if self._timeout_occurred: + while True: + time.sleep(0) + + def _timed_out(self): + with self._lock: + if self._finished: + return + self._timeout_occurred = True + self._raise_timeout() + + def _raise_timeout(self): + # See, for example, http://tomerfiliba.com/recipes/Thread2/ + # for more information about using PyThreadState_SetAsyncExc + tid = ctypes.c_long(self._runner_thread_id) + error = ctypes.py_object(type(self._error)) + while ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, error) > 1: + ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) + time.sleep(0) # give time for other threads diff --git a/robot/lib/python3.8/site-packages/robot/running/usererrorhandler.py b/robot/lib/python3.8/site-packages/robot/running/usererrorhandler.py new file mode 100644 index 0000000000000000000000000000000000000000..c3a47f1d455fe3eea2d09cdc5800025b7d772786 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/usererrorhandler.py @@ -0,0 +1,70 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.model import Tags +from robot.result import Keyword as KeywordResult + +from .arguments import ArgumentSpec +from .statusreporter import StatusReporter + + +class UserErrorHandler(object): + """Created if creating handlers fail -- running raises DataError. + + The idea is not to raise DataError at processing time and prevent all + tests in affected test case file from executing. Instead UserErrorHandler + is created and if it is ever run DataError is raised then. + """ + + def __init__(self, error, name, libname=None): + """ + :param robot.errors.DataError error: Occurred error. + :param str name: Name of the affected keyword. + :param str libname: Name of the affected library or resource. + """ + self.name = name + self.libname = libname + self.error = error + self.source = None + self.lineno = -1 + self.arguments = ArgumentSpec() + self.timeout = None + self.tags = Tags() + + @property + def longname(self): + return '%s.%s' % (self.libname, self.name) if self.libname else self.name + + @property + def doc(self): + return '*Creating keyword failed:* %s' % self.error + + @property + def shortdoc(self): + return self.doc.splitlines()[0] + + def create_runner(self, name): + return self + + def run(self, kw, context): + result = KeywordResult(kwname=self.name, + libname=self.libname, + args=kw.args, + assign=kw.assign, + type=kw.type) + with StatusReporter(context, result): + raise self.error + + dry_run = run diff --git a/robot/lib/python3.8/site-packages/robot/running/userkeyword.py b/robot/lib/python3.8/site-packages/robot/running/userkeyword.py new file mode 100644 index 0000000000000000000000000000000000000000..65a3898e2e15093264f15892bdcfc381a8705645 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/userkeyword.py @@ -0,0 +1,111 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from robot.errors import DataError +from robot.output import LOGGER +from robot.utils import getshortdoc, unic + +from .arguments import EmbeddedArguments, UserKeywordArgumentParser +from .handlerstore import HandlerStore +from .userkeywordrunner import UserKeywordRunner, EmbeddedArgumentsRunner +from .usererrorhandler import UserErrorHandler + + +class UserLibrary(object): + TEST_CASE_FILE_TYPE = HandlerStore.TEST_CASE_FILE_TYPE + RESOURCE_FILE_TYPE = HandlerStore.RESOURCE_FILE_TYPE + + def __init__(self, resource, source_type=RESOURCE_FILE_TYPE): + source = resource.source + basename = os.path.basename(source) if source else None + self.name = os.path.splitext(basename)[0] \ + if source_type == self.RESOURCE_FILE_TYPE else None + self.doc = resource.doc + self.handlers = HandlerStore(basename, source_type) + self.source = source + self.source_type = source_type + for kw in resource.keywords: + try: + handler = self._create_handler(kw) + except DataError as error: + handler = UserErrorHandler(error, kw.name, self.name) + self._log_creating_failed(handler, error) + embedded = isinstance(handler, EmbeddedArgumentsHandler) + try: + self.handlers.add(handler, embedded) + except DataError as error: + self._log_creating_failed(handler, error) + + def _create_handler(self, kw): + embedded = EmbeddedArguments(kw.name) + if not embedded: + return UserKeywordHandler(kw, self.name) + if kw.args: + raise DataError('Keyword cannot have both normal and embedded ' + 'arguments.') + return EmbeddedArgumentsHandler(kw, self.name, embedded) + + def _log_creating_failed(self, handler, error): + LOGGER.error("Error in %s '%s': Creating keyword '%s' failed: %s" + % (self.source_type.lower(), self.source, + handler.name, error.message)) + + +# TODO: Should be merged with running.model.UserKeyword + +class UserKeywordHandler(object): + + def __init__(self, keyword, libname): + self.name = keyword.name + self.libname = libname + self.doc = unic(keyword.doc) + self.source = keyword.source + self.lineno = keyword.lineno + self.tags = keyword.tags + self.arguments = UserKeywordArgumentParser().parse(tuple(keyword.args), + self.longname) + self._kw = keyword + self.timeout = keyword.timeout + self.keywords = keyword.keywords.normal + self.return_value = tuple(keyword.return_) + self.teardown = keyword.keywords.teardown + + @property + def longname(self): + return '%s.%s' % (self.libname, self.name) if self.libname else self.name + + @property + def shortdoc(self): + return getshortdoc(self.doc) + + def create_runner(self, name): + return UserKeywordRunner(self) + + +class EmbeddedArgumentsHandler(UserKeywordHandler): + + def __init__(self, keyword, libname, embedded): + UserKeywordHandler.__init__(self, keyword, libname) + self.keyword = keyword + self.embedded_name = embedded.name + self.embedded_args = embedded.args + + def matches(self, name): + return self.embedded_name.match(name) is not None + + def create_runner(self, name): + return EmbeddedArgumentsRunner(self, name) diff --git a/robot/lib/python3.8/site-packages/robot/running/userkeywordrunner.py b/robot/lib/python3.8/site-packages/robot/running/userkeywordrunner.py new file mode 100644 index 0000000000000000000000000000000000000000..a6557a1f0169a522aa5304b33321a0db68853c18 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/running/userkeywordrunner.py @@ -0,0 +1,247 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from itertools import chain + +from robot.errors import (ExecutionFailed, ExecutionPassed, ExecutionStatus, + ExitForLoop, ContinueForLoop, DataError, + PassExecution, ReturnFromKeyword, + UserKeywordExecutionFailed, VariableError) +from robot.result import Keyword as KeywordResult +from robot.utils import getshortdoc, DotDict, prepr, split_tags_from_doc +from robot.variables import is_list_variable, VariableAssignment + +from .arguments import DefaultValue +from .statusreporter import StatusReporter +from .steprunner import StepRunner +from .timeouts import KeywordTimeout + + +class UserKeywordRunner(object): + + def __init__(self, handler, name=None): + self._handler = handler + self.name = name or handler.name + + @property + def longname(self): + libname = self._handler.libname + return '%s.%s' % (libname, self.name) if libname else self.name + + @property + def libname(self): + return self._handler.libname + + @property + def arguments(self): + return self._handler.arguments + + def run(self, kw, context): + assignment = VariableAssignment(kw.assign) + result = self._get_result(kw, assignment, context.variables) + with StatusReporter(context, result): + with assignment.assigner(context) as assigner: + return_value = self._run(context, kw.args, result) + assigner.assign(return_value) + return return_value + + def _get_result(self, kw, assignment, variables): + handler = self._handler + doc = variables.replace_string(handler.doc, ignore_errors=True) + doc, tags = split_tags_from_doc(doc) + tags = variables.replace_list(handler.tags, ignore_errors=True) + tags + return KeywordResult(kwname=self.name, + libname=handler.libname, + doc=getshortdoc(doc), + args=kw.args, + assign=tuple(assignment), + tags=tags, + type=kw.type) + + def _run(self, context, args, result): + variables = context.variables + args = self._resolve_arguments(args, variables) + with context.user_keyword: + self._set_arguments(args, context) + timeout = self._get_timeout(variables) + if timeout is not None: + result.timeout = str(timeout) + with context.timeout(timeout): + exception, return_ = self._execute(context) + if exception and not exception.can_continue(context.in_teardown): + raise exception + return_value = self._get_return_value(variables, return_) + if exception: + exception.return_value = return_value + raise exception + return return_value + + def _get_timeout(self, variables=None): + timeout = self._handler.timeout + return KeywordTimeout(timeout, variables) if timeout else None + + def _resolve_arguments(self, arguments, variables=None): + return self.arguments.resolve(arguments, variables) + + def _set_arguments(self, arguments, context): + positional, named = arguments + variables = context.variables + args, kwargs = self.arguments.map(positional, named, + replace_defaults=False) + self._set_variables(args, kwargs, variables) + context.output.trace(lambda: self._trace_log_args_message(variables)) + + def _set_variables(self, positional, kwargs, variables): + spec = self.arguments + args, varargs = self._split_args_and_varargs(positional) + kwonly, kwargs = self._split_kwonly_and_kwargs(kwargs) + for name, value in chain(zip(spec.positional, args), kwonly): + if isinstance(value, DefaultValue): + value = value.resolve(variables) + variables['${%s}' % name] = value + if spec.varargs: + variables['@{%s}' % spec.varargs] = varargs + if spec.kwargs: + variables['&{%s}' % spec.kwargs] = DotDict(kwargs) + + def _split_args_and_varargs(self, args): + if not self.arguments.varargs: + return args, [] + positional = len(self.arguments.positional) + return args[:positional], args[positional:] + + def _split_kwonly_and_kwargs(self, all_kwargs): + kwonly = [] + kwargs = [] + for name, value in all_kwargs: + target = kwonly if name in self.arguments.kwonlyargs else kwargs + target.append((name, value)) + return kwonly, kwargs + + def _trace_log_args_message(self, variables): + args = ['${%s}' % arg for arg in self.arguments.positional] + if self.arguments.varargs: + args.append('@{%s}' % self.arguments.varargs) + if self.arguments.kwargs: + args.append('&{%s}' % self.arguments.kwargs) + return self._format_trace_log_args_message(args, variables) + + def _format_trace_log_args_message(self, args, variables): + args = ['%s=%s' % (name, prepr(variables[name])) for name in args] + return 'Arguments: [ %s ]' % ' | '.join(args) + + def _execute(self, context): + handler = self._handler + if not (handler.keywords or handler.return_value): + raise DataError("User keyword '%s' contains no keywords." % self.name) + if context.dry_run and 'robot:no-dry-run' in handler.tags: + return None, None + error = return_ = pass_ = None + try: + StepRunner(context).run_steps(handler.keywords) + except ReturnFromKeyword as exception: + return_ = exception + error = exception.earlier_failures + except (ExitForLoop, ContinueForLoop) as exception: + pass_ = exception + except ExecutionPassed as exception: + pass_ = exception + error = exception.earlier_failures + if error: + error.continue_on_failure = False + except ExecutionFailed as exception: + error = exception + with context.keyword_teardown(error): + td_error = self._run_teardown(context) + if error or td_error: + error = UserKeywordExecutionFailed(error, td_error) + return error or pass_, return_ + + def _get_return_value(self, variables, return_): + ret = self._handler.return_value if not return_ else return_.return_value + if not ret: + return None + contains_list_var = any(is_list_variable(item) for item in ret) + try: + ret = variables.replace_list(ret) + except DataError as err: + raise VariableError('Replacing variables from keyword return ' + 'value failed: %s' % err.message) + if len(ret) != 1 or contains_list_var: + return ret + return ret[0] + + def _run_teardown(self, context): + if not self._handler.teardown: + return None + try: + name = context.variables.replace_string(self._handler.teardown.name) + except DataError as err: + if context.dry_run: + return None + return ExecutionFailed(err.message, syntax=True) + if name.upper() in ('', 'NONE'): + return None + try: + StepRunner(context).run_step(self._handler.teardown, name) + except PassExecution: + return None + except ExecutionStatus as err: + return err + return None + + def dry_run(self, kw, context): + assignment = VariableAssignment(kw.assign) + result = self._get_result(kw, assignment, context.variables) + with StatusReporter(context, result): + assignment.validate_assignment() + self._dry_run(context, kw.args, result) + + def _dry_run(self, context, args, result): + self._resolve_arguments(args) + with context.user_keyword: + timeout = self._get_timeout() + if timeout: + result.timeout = str(timeout) + error, _ = self._execute(context) + if error: + raise error + + +class EmbeddedArgumentsRunner(UserKeywordRunner): + + def __init__(self, handler, name): + UserKeywordRunner.__init__(self, handler, name) + match = handler.embedded_name.match(name) + if not match: + raise ValueError('Does not match given name') + self.embedded_args = list(zip(handler.embedded_args, match.groups())) + + def _resolve_arguments(self, args, variables=None): + # Validates that no arguments given. + self.arguments.resolve(args, variables) + if not variables: + return [] + return [(n, variables.replace_scalar(v)) for n, v in self.embedded_args] + + def _set_arguments(self, embedded_args, context): + variables = context.variables + for name, value in embedded_args: + variables['${%s}' % name] = value + context.output.trace(lambda: self._trace_log_args_message(variables)) + + def _trace_log_args_message(self, variables): + args = ['${%s}' % arg for arg, _ in self.embedded_args] + return self._format_trace_log_args_message(args, variables) diff --git a/robot/lib/python3.8/site-packages/robot/testdoc.py b/robot/lib/python3.8/site-packages/robot/testdoc.py new file mode 100644 index 0000000000000000000000000000000000000000..4f974aa0a037acca029ac3dbb0fc83816b73db72 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/testdoc.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python + +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module implementing the command line entry point for the `Testdoc` tool. + +This module can be executed from the command line using the following +approaches:: + + python -m robot.testdoc + python path/to/robot/testdoc.py + +Instead of ``python`` it is possible to use also other Python interpreters. + +This module also provides :func:`testdoc` and :func:`testdoc_cli` functions +that can be used programmatically. Other code is for internal usage. +""" + +import os.path +import sys +import time + +# Allows running as a script. __name__ check needed with multiprocessing: +# https://github.com/robotframework/robotframework/issues/1137 +if 'robot' not in sys.modules and __name__ == '__main__': + import pythonpathsetter + +from robot.conf import RobotSettings +from robot.htmldata import HtmlFileWriter, ModelWriter, JsonWriter, TESTDOC +from robot.running import TestSuiteBuilder +from robot.utils import (abspath, Application, file_writer, get_link_path, + html_escape, html_format, IRONPYTHON, is_string, + PY_VERSION, secs_to_timestr, seq2str2, + timestr_to_secs, unescape) + + +# http://ironpython.codeplex.com/workitem/31549 +if IRONPYTHON and PY_VERSION < (2, 7, 2): + int = long + + +USAGE = """robot.testdoc -- Robot Framework test data documentation tool + +Version: + +Usage: python -m robot.testdoc [options] data_sources output_file + +Testdoc generates a high level test documentation based on Robot Framework +test data. Generated documentation includes name, documentation and other +metadata of each test suite and test case, as well as the top-level keywords +and their arguments. + +Options +======= + + -T --title title Set the title of the generated documentation. + Underscores in the title are converted to spaces. + The default title is the name of the top level suite. + -N --name name Override the name of the top level suite. + -D --doc document Override the documentation of the top level suite. + -M --metadata name:value * Set/override metadata of the top level suite. + -G --settag tag * Set given tag(s) to all test cases. + -t --test name * Include tests by name. + -s --suite name * Include suites by name. + -i --include tag * Include tests by tags. + -e --exclude tag * Exclude tests by tags. + -A --argumentfile path * Text file to read more arguments from. Use special + path `STDIN` to read contents from the standard input + stream. File can have both options and data sources + one per line. Contents do not need to be escaped but + spaces in the beginning and end of lines are removed. + Empty lines and lines starting with a hash character + (#) are ignored. New in Robot Framework 3.0.2. + Example file: + | --name Example + | # This is a comment line + | my_tests.robot + | output.html + Examples: + --argumentfile argfile.txt --argumentfile STDIN + -h -? --help Print this help. + +All options except --title have exactly same semantics as same options have +when executing test cases. + +Execution +========= + +Data can be given as a single file, directory, or as multiple files and +directories. In all these cases, the last argument must be the file where +to write the output. The output is always created in HTML format. + +Testdoc works with all interpreters supported by Robot Framework (Python, +Jython and IronPython). It can be executed as an installed module like +`python -m robot.testdoc` or as a script like `python path/robot/testdoc.py`. + +Examples: + + python -m robot.testdoc my_test.robot testdoc.html + jython -m robot.testdoc -N smoke_tests -i smoke path/to/my_tests smoke.html + ipy path/to/robot/testdoc.py first_suite.txt second_suite.txt output.html + +For more information about Testdoc and other built-in tools, see +http://robotframework.org/robotframework/#built-in-tools. +""" + + +class TestDoc(Application): + + def __init__(self): + Application.__init__(self, USAGE, arg_limits=(2,)) + + def main(self, datasources, title=None, **options): + outfile = abspath(datasources.pop()) + suite = TestSuiteFactory(datasources, **options) + self._write_test_doc(suite, outfile, title) + self.console(outfile) + + def _write_test_doc(self, suite, outfile, title): + with file_writer(outfile, usage='Testdoc output') as output: + model_writer = TestdocModelWriter(output, suite, title) + HtmlFileWriter(output, model_writer).write(TESTDOC) + + +def TestSuiteFactory(datasources, **options): + settings = RobotSettings(options) + if is_string(datasources): + datasources = [datasources] + suite = TestSuiteBuilder(process_curdir=False).build(*datasources) + suite.configure(**settings.suite_config) + return suite + + +class TestdocModelWriter(ModelWriter): + + def __init__(self, output, suite, title=None): + self._output = output + self._output_path = getattr(output, 'name', None) + self._suite = suite + self._title = title.replace('_', ' ') if title else suite.name + + def write(self, line): + self._output.write('\n') + + def write_data(self): + model = { + 'suite': JsonConverter(self._output_path).convert(self._suite), + 'title': self._title, + 'generated': int(time.time() * 1000) + } + JsonWriter(self._output).write_json('testdoc = ', model) + + +class JsonConverter(object): + + def __init__(self, output_path=None): + self._output_path = output_path + + def convert(self, suite): + return self._convert_suite(suite) + + def _convert_suite(self, suite): + return { + 'source': suite.source or '', + 'relativeSource': self._get_relative_source(suite.source), + 'id': suite.id, + 'name': self._escape(suite.name), + 'fullName': self._escape(suite.longname), + 'doc': self._html(suite.doc), + 'metadata': [(self._escape(name), self._html(value)) + for name, value in suite.metadata.items()], + 'numberOfTests': suite.test_count , + 'suites': self._convert_suites(suite), + 'tests': self._convert_tests(suite), + 'keywords': list(self._convert_keywords(suite)) + } + + def _get_relative_source(self, source): + if not source or not self._output_path: + return '' + return get_link_path(source, os.path.dirname(self._output_path)) + + def _escape(self, item): + return html_escape(item) + + def _html(self, item): + return html_format(unescape(item)) + + def _convert_suites(self, suite): + return [self._convert_suite(s) for s in suite.suites] + + def _convert_tests(self, suite): + return [self._convert_test(t) for t in suite.tests] + + def _convert_test(self, test): + return { + 'name': self._escape(test.name), + 'fullName': self._escape(test.longname), + 'id': test.id, + 'doc': self._html(test.doc), + 'tags': [self._escape(t) for t in test.tags], + 'timeout': self._get_timeout(test.timeout), + 'keywords': list(self._convert_keywords(test)) + } + + def _convert_keywords(self, item): + for kw in getattr(item, 'keywords', []): + if kw.type == kw.SETUP_TYPE: + yield self._convert_keyword(kw, 'SETUP') + elif kw.type == kw.TEARDOWN_TYPE: + yield self._convert_keyword(kw, 'TEARDOWN') + elif kw.type == kw.FOR_LOOP_TYPE: + yield self._convert_for_loop(kw) + else: + yield self._convert_keyword(kw, 'KEYWORD') + + def _convert_for_loop(self, kw): + return { + 'name': self._escape(self._get_for_loop(kw)), + 'arguments': '', + 'type': 'FOR' + } + + def _convert_keyword(self, kw, kw_type): + return { + 'name': self._escape(self._get_kw_name(kw)), + 'arguments': self._escape(', '.join(kw.args)), + 'type': kw_type + } + + def _get_kw_name(self, kw): + if kw.assign: + return '%s = %s' % (', '.join(a.rstrip('= ') for a in kw.assign), kw.name) + return kw.name + + def _get_for_loop(self, kw): + joiner = ' %s ' % kw.flavor + return ', '.join(kw.variables) + joiner + seq2str2(kw.values) + + def _get_timeout(self, timeout): + if timeout is None: + return '' + try: + tout = secs_to_timestr(timestr_to_secs(timeout)) + except ValueError: + tout = timeout + return tout + + +def testdoc_cli(arguments): + """Executes `Testdoc` similarly as from the command line. + + :param arguments: command line arguments as a list of strings. + + For programmatic usage the :func:`testdoc` function is typically better. It + has a better API for that and does not call :func:`sys.exit` like + this function. + + Example:: + + from robot.testdoc import testdoc_cli + + testdoc_cli(['--title', 'Test Plan', 'mytests', 'plan.html']) + """ + TestDoc().execute_cli(arguments) + + +def testdoc(*arguments, **options): + """Executes `Testdoc` programmatically. + + Arguments and options have same semantics, and options have same names, + as arguments and options to Testdoc. + + Example:: + + from robot.testdoc import testdoc + + testdoc('mytests', 'plan.html', title='Test Plan') + """ + TestDoc().execute(*arguments, **options) + + +if __name__ == '__main__': + testdoc_cli(sys.argv[1:]) diff --git a/robot/lib/python3.8/site-packages/robot/tidy.py b/robot/lib/python3.8/site-packages/robot/tidy.py new file mode 100644 index 0000000000000000000000000000000000000000..3074c00a18628952936e9d3dc5227394ac168d96 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/tidy.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python + +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module implementing the command line entry point for the `Tidy` tool. + +This module can be executed from the command line using the following +approaches:: + + python -m robot.tidy + python path/to/robot/tidy.py + +Instead of ``python`` it is possible to use also other Python interpreters. + +This module also provides :class:`Tidy` class and :func:`tidy_cli` function +that can be used programmatically. Other code is for internal usage. +""" + +import os +import sys + +# Allows running as a script. __name__ check needed with multiprocessing: +# https://github.com/robotframework/robotframework/issues/1137 +if 'robot' not in sys.modules and __name__ == '__main__': + import pythonpathsetter + +from robot.errors import DataError +from robot.parsing import (get_model, SuiteStructureBuilder, + SuiteStructureVisitor) +from robot.tidypkg import (Aligner, Cleaner, NewlineNormalizer, + SeparatorNormalizer) +from robot.utils import Application, file_writer + +USAGE = """robot.tidy -- Robot Framework data clean-up tool + +Version: + +Usage: python -m robot.tidy [options] input + or: python -m robot.tidy [options] input [output] + or: python -m robot.tidy --inplace [options] input [more inputs] + or: python -m robot.tidy --recursive [options] directory + +Tidy tool can be used to clean up Robot Framework data. It, for example, uses +headers and settings consistently and adds consistent amount of whitespace +between sections, keywords and their arguments, and other pieces of the data. +It also converts old syntax to new syntax when appropriate. + +When tidying a single file, the output is written to the console by default, +but an optional output file can be given as well. Files can also be modified +in-place using --inplace and --recursive options. + +All output files are written using UTF-8 encoding. Outputs written to the +console use the current console encoding. + +Options +======= + + -i --inplace Tidy given file(s) so that original file(s) are overwritten. + When this option is used, it is possible to give multiple + input files. + -r --recursive Process given directory recursively. Files in the directory + are processed in-place similarly as when --inplace option + is used. Does not process referenced resource files. + -p --usepipes Use pipe ('|') as a column separator in the plain text format. + -s --spacecount number + The number of spaces between cells in the plain text format. + Default is 4. + -l --lineseparator native|windows|unix + Line separator to use in outputs. The default is 'native'. + native: use operating system's native line separators + windows: use Windows line separators (CRLF) + unix: use Unix line separators (LF) + -h -? --help Show this help. + +Examples +======== + + python -m robot.tidy example.robot + python -m robot.tidy messed_up_data.robot cleaned_up_data.robot + python -m robot.tidy --inplace example.robot + python -m robot.tidy --recursive path/to/tests + +Alternative execution +===================== + +In the above examples Tidy is used only with Python, but it works also with +Jython and IronPython. Above it is executed as an installed module, but it +can also be run as a script like `python path/robot/tidy.py`. + +For more information about Tidy and other built-in tools, see +http://robotframework.org/robotframework/#built-in-tools. +""" + + +class Tidy(SuiteStructureVisitor): + """Programmatic API for the `Tidy` tool. + + Arguments accepted when creating an instance have same semantics as + Tidy command line options with same names. + """ + + def __init__(self, space_count=4, use_pipes=False, + line_separator=os.linesep): + self.space_count = space_count + self.use_pipes = use_pipes + self.line_separator = line_separator + self.short_test_name_length = 18 + self.setting_and_variable_name_length = 14 + + def file(self, path, outpath=None): + """Tidy a file. + + :param path: Path of the input file. + :param outpath: Path of the output file. If not given, output is + returned. + + Use :func:`inplace` to tidy files in-place. + """ + with self._get_output(outpath) as writer: + self._tidy(get_model(path), writer) + if not outpath: + return writer.getvalue().replace('\r\n', '\n') + + def _get_output(self, path): + return file_writer(path, newline='', usage='Tidy output') + + def inplace(self, *paths): + """Tidy file(s) in-place. + + :param paths: Paths of the files to to process. + """ + for path in paths: + model = get_model(path) + with self._get_output(path) as output: + self._tidy(model, output) + + def directory(self, path): + """Tidy a directory. + + :param path: Path of the directory to process. + + All files in a directory, recursively, are processed in-place. + """ + data = SuiteStructureBuilder().build([path]) + data.visit(self) + + def _tidy(self, model, output): + Cleaner().visit(model) + NewlineNormalizer(self.line_separator, + self.short_test_name_length).visit(model) + SeparatorNormalizer(self.use_pipes, self.space_count).visit(model) + Aligner(self.short_test_name_length, + self.setting_and_variable_name_length, + self.use_pipes).visit(model) + model.save(output) + + def visit_file(self, file): + self.inplace(file.source) + + def visit_directory(self, directory): + if directory.init_file: + self.inplace(directory.init_file) + for child in directory.children: + child.visit(self) + + +class TidyCommandLine(Application): + """Command line interface for the `Tidy` tool. + + Typically :func:`tidy_cli` is a better suited for command line style + usage and :class:`Tidy` for other programmatic usage. + """ + + def __init__(self): + Application.__init__(self, USAGE, arg_limits=(1,)) + + def main(self, arguments, recursive=False, inplace=False, + usepipes=False, spacecount=4, lineseparator=os.linesep): + tidy = Tidy(use_pipes=usepipes, space_count=spacecount, + line_separator=lineseparator) + if recursive: + tidy.directory(arguments[0]) + elif inplace: + tidy.inplace(*arguments) + else: + output = tidy.file(*arguments) + self.console(output) + + def validate(self, opts, args): + validator = ArgumentValidator() + opts['recursive'], opts['inplace'] = validator.mode_and_args(args, + **opts) + opts['lineseparator'] = validator.line_sep(**opts) + if not opts['spacecount']: + opts.pop('spacecount') + else: + opts['spacecount'] = validator.spacecount(opts['spacecount']) + return opts, args + + +class ArgumentValidator(object): + + def mode_and_args(self, args, recursive, inplace, **others): + recursive, inplace = bool(recursive), bool(inplace) + validators = {(True, True): self._recursive_and_inplace_together, + (True, False): self._recursive_mode_arguments, + (False, True): self._inplace_mode_arguments, + (False, False): self._default_mode_arguments} + validator = validators[(recursive, inplace)] + validator(args) + return recursive, inplace + + def _recursive_and_inplace_together(self, args): + raise DataError('--recursive and --inplace can not be used together.') + + def _recursive_mode_arguments(self, args): + if len(args) != 1: + raise DataError('--recursive requires exactly one argument.') + if not os.path.isdir(args[0]): + raise DataError('--recursive requires input to be a directory.') + + def _inplace_mode_arguments(self, args): + if not all(os.path.isfile(path) for path in args): + raise DataError('--inplace requires inputs to be files.') + + def _default_mode_arguments(self, args): + if len(args) not in (1, 2): + raise DataError('Default mode requires 1 or 2 arguments.') + if not os.path.isfile(args[0]): + raise DataError('Default mode requires input to be a file.') + + def line_sep(self, lineseparator, **others): + values = {'native': os.linesep, 'windows': '\r\n', 'unix': '\n'} + try: + return values[(lineseparator or 'native').lower()] + except KeyError: + raise DataError("Invalid line separator '%s'." % lineseparator) + + def spacecount(self, spacecount): + try: + spacecount = int(spacecount) + if spacecount < 2: + raise ValueError + except ValueError: + raise DataError('--spacecount must be an integer greater than 1.') + return spacecount + + +def tidy_cli(arguments): + """Executes `Tidy` similarly as from the command line. + + :param arguments: Command line arguments as a list of strings. + + Example:: + + from robot.tidy import tidy_cli + + tidy_cli(['--spacecount', '2', 'tests.robot']) + """ + TidyCommandLine().execute_cli(arguments) + + +if __name__ == '__main__': + tidy_cli(sys.argv[1:]) diff --git a/robot/lib/python3.8/site-packages/robot/tidypkg/__init__.py b/robot/lib/python3.8/site-packages/robot/tidypkg/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7327a8c334dfd628260d5f9cdef6033403f9b548 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/tidypkg/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .transformers import Aligner, Cleaner, NewlineNormalizer, SeparatorNormalizer diff --git a/robot/lib/python3.8/site-packages/robot/tidypkg/transformers.py b/robot/lib/python3.8/site-packages/robot/tidypkg/transformers.py new file mode 100644 index 0000000000000000000000000000000000000000..9e442dd9c5c4fc23cbaa2e4a31a70126fe5a01d7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/tidypkg/transformers.py @@ -0,0 +1,414 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from itertools import takewhile + +from robot.parsing import Token, ModelTransformer +from robot.parsing.model.statements import EmptyLine, End +from robot.utils import normalize_whitespace + + +class Cleaner(ModelTransformer): + """Clean up and normalize data. + + Following transformations are made: + 1) section headers are normalized to format `*** Section Name ***` + 2) setting names are normalize in setting table and in test cases and + user keywords to format `Setting Name` or `[Setting Name]` + 3) settings without values are removed + 4) Empty lines after section headers and within items are removed + 5) For loop declaration and end tokens are normalized to `FOR` and `END` + 6) Old style for loop indent (i.e. a cell with only a `\\`) are removed + """ + + def __init__(self): + self.in_data_section = False + + def visit_CommentSection(self, section): + self.generic_visit(section) + return section + + def visit_Section(self, section): + self.in_data_section = True + self._normalize_section_header(section) + self.generic_visit(section) + return section + + def _normalize_section_header(self, section): + header_token = section.header.data_tokens[0] + normalized = self._normalize_name(header_token.value, remove='*') + header_token.value = '*** %s ***' % normalized + + def visit_Statement(self, statement): + statement.tokens = list(self._remove_old_for_loop_indent(statement)) + if statement.type in Token.SETTING_TOKENS: + self._normalize_setting_name(statement) + self.generic_visit(statement) + if self._is_setting_without_value(statement) or \ + self._is_empty_line_in_data(statement): + return None + if self.in_data_section: + self._remove_empty_lines_within_statement(statement) + return statement + + def _remove_old_for_loop_indent(self, statement): + prev_was_for_indent = False + for t in statement.tokens: + if t.type == Token.OLD_FOR_INDENT: + prev_was_for_indent = True + continue + elif prev_was_for_indent and t.type == Token.SEPARATOR: + prev_was_for_indent = False + continue + else: + yield t + + def _normalize_setting_name(self, statement): + name = statement.data_tokens[0].value + if name.startswith('['): + cleaned = '[%s]' % self._normalize_name(name[1:-1]) + else: + cleaned = self._normalize_name(name) + statement.data_tokens[0].value = cleaned + + def _normalize_name(self, marker, remove=None): + if remove: + marker = marker.replace(remove, '') + return normalize_whitespace(marker).strip().title() + + def _is_setting_without_value(self, statement): + return statement.type in Token.SETTING_TOKENS and \ + len(statement.data_tokens) == 1 + + def _is_empty_line_in_data(self, statement): + return self.in_data_section and statement.type == Token.EOL + + def _remove_empty_lines_within_statement(self, statement): + new_tokens = [] + for line in statement.lines: + if len(line) == 1 and line[0].type == Token.EOL: + continue + new_tokens.extend(line) + statement.tokens = new_tokens + + def visit_ForLoop(self, loop): + loop.header.data_tokens[0].value = 'FOR' + if loop.end: + loop.end.data_tokens[0].value = 'END' + else: + loop.end = End([Token(Token.SEPARATOR), Token(Token.END, 'END')]) + self.generic_visit(loop) + return loop + + +class NewlineNormalizer(ModelTransformer): + """Normalize new lines in test data + + After this transformation, there is exactly one empty line between each + section and between each test or user keyword. + """ + + def __init__(self, newline, short_test_name_length): + self.newline = newline + self.short_test_name_length = short_test_name_length + self.custom_test_section_headers = False + self.last_test = None + self.last_keyword = None + self.last_section = None + + def visit_File(self, node): + self.last_section = node.sections[-1] if node.sections else None + return self.generic_visit(node) + + def visit_Section(self, node): + if node is not self.last_section: + node.body.append(EmptyLine.from_value(self.newline)) + return self.generic_visit(node) + + def visit_CommentSection(self, node): + return self.generic_visit(node) + + def visit_TestCaseSection(self, node): + self.last_test = node.body[-1] if node.body else None + self.custom_test_section_headers = len(node.header.data_tokens) > 1 + section = self.visit_Section(node) + self.custom_test_section_headers = False + return section + + def visit_TestCase(self, node): + if not node.body or node is not self.last_test: + node.body.append(EmptyLine.from_value(self.newline)) + return self.generic_visit(node) + + def visit_KeywordSection(self, node): + self.last_keyword = node.body[-1] if node.body else None + return self.visit_Section(node) + + def visit_Keyword(self, node): + if not node.body or node is not self.last_keyword: + node.body.append(EmptyLine.from_value(self.newline)) + return self.generic_visit(node) + + def visit_Statement(self, statement): + if statement[-1].type != Token.EOL: + if not self._should_write_content_after_name(statement): + statement.tokens.append(Token(Token.EOL, self.newline)) + for line in statement.lines: + if line[-1].type == Token.EOL: + line[-1].value = self.newline + return statement + + def _should_write_content_after_name(self, statement): + return (statement.type in (Token.TESTCASE_NAME, Token.KEYWORD_NAME) and + self.custom_test_section_headers and + len(statement.tokens[0].value) < self.short_test_name_length) + + +class SeparatorNormalizer(ModelTransformer): + """Make separators and indentation consistent.""" + + def __init__(self, use_pipes, space_count): + self.use_pipes = use_pipes + self.space_count = space_count + self.indent = 0 + + def visit_TestCase(self, node): + self.visit_Statement(node.header) + self.indent += 1 + node.body = [self.visit(item) for item in node.body] + self.indent -= 1 + return node + + def visit_Keyword(self, node): + self.visit_Statement(node.header) + self.indent += 1 + node.body = [self.visit(item) for item in node.body] + self.indent -= 1 + return node + + def visit_ForLoop(self, node): + self.visit_Statement(node.header) + self.indent += 1 + node.body = [self.visit(item) for item in node.body] + self.indent -= 1 + self.visit_Statement(node.end) + return node + + def visit_Statement(self, statement): + has_pipes = statement.tokens[0].value.startswith('|') + if self.use_pipes: + return self._handle_pipes(statement, has_pipes) + return self._handle_spaces(statement, has_pipes) + + def _handle_spaces(self, statement, has_pipes=False): + new_tokens = [] + for line in statement.lines: + if has_pipes and len(line) > 1: + line = self._remove_consecutive_separators(line) + new_tokens.extend([self._normalize_spaces(i, t, len(line)) + for i, t in enumerate(line)]) + statement.tokens = new_tokens + self.generic_visit(statement) + return statement + + def _remove_consecutive_separators(self, line): + sep_count = len(list( + takewhile(lambda t: t.type == Token.SEPARATOR, line) + )) + return line[sep_count - 1:] + + def _normalize_spaces(self, index, token, line_length): + if token.type == Token.SEPARATOR: + spaces = self.space_count * self.indent \ + if index == 0 else self.space_count + token.value = ' ' * spaces + # The last token is always EOL, this removes all dangling whitespace + # from the token before the EOL + if index == line_length - 2: + token.value = token.value.rstrip() + return token + + def _handle_pipes(self, statement, has_pipes=False): + new_tokens = [] + for line in statement.lines: + if len(line) == 1 and line[0].type == Token.EOL: + new_tokens.extend(line) + continue + + if not has_pipes: + line = self._insert_leading_and_trailing_separators(line) + for index, token in enumerate(line): + if token.type == Token.SEPARATOR: + if index == 0: + if self.indent: + token.value = '| ' + else: + token.value = '| ' + elif index < self.indent: + token.value = ' | ' + elif len(line) > 1 and index == len(line) - 2: + # This is the separator before EOL. + token.value = ' |' + else: + token.value = ' | ' + new_tokens.extend(line) + statement.tokens = new_tokens + return statement + + def _insert_leading_and_trailing_separators(self, line): + """Add missing separators to the beginning and the end of the line. + + When converting from spaces to pipes, a separator token is needed + in the beginning of the line, for each indent level and in the + end of the line. + """ + separators_needed = 1 + if self.indent > 1: + # Space format has 1 separator token regardless of the indent level. + # With pipes, we need to add one separator for each indent level + # beyond 1. + separators_needed += self.indent - 1 + for _ in range(separators_needed): + line = [Token(Token.SEPARATOR, '')] + line + if len(line) > 1: + if line[-2].type != Token.SEPARATOR: + line = line[:-1] + [Token(Token.SEPARATOR, ''), line[-1]] + return line + + +class ColumnAligner(ModelTransformer): + + def __init__(self, short_test_name_length, widths): + self.short_test_name_length = short_test_name_length + self.widths = widths + self.test_name_len = 0 + self.indent = 0 + self.first_statement_after_name_seen = False + + def visit_TestCase(self, node): + self.first_statement_after_name_seen = False + return self.generic_visit(node) + + def visit_ForLoop(self, node): + self.indent += 1 + self.generic_visit(node) + self.indent -= 1 + return node + + def visit_Statement(self, statement): + if statement.type == Token.TESTCASE_NAME: + self.test_name_len = len(statement.tokens[0].value) + elif statement.type == Token.TESTCASE_HEADER: + self.align_header(statement) + else: + self.align_statement(statement) + return statement + + def align_header(self, statement): + for token, width in zip(statement.data_tokens[:-1], self.widths): + token.value = token.value.ljust(width) + + def align_statement(self, statement): + for line in statement.lines: + line = [t for t in line if t.type + not in (Token.SEPARATOR, Token.EOL)] + line_pos = 0 + exp_pos = 0 + widths = self.widths_for_line(line) + for token, width in zip(line, widths): + exp_pos += width + if self.should_write_content_after_name(line_pos): + exp_pos -= self.test_name_len + self.first_statement_after_name_seen = True + token.value = (exp_pos - line_pos) * ' ' + token.value + line_pos += len(token.value) + + def widths_for_line(self, line): + if self.indent > 0 and self._should_be_indented(line): + widths = self.widths[1:] + widths[0] = widths[0] + self.widths[0] + return widths + return self.widths + + def _should_be_indented(self, line): + return line[0].type in (Token.KEYWORD, Token.ASSIGN, + Token.CONTINUATION) + + def should_write_content_after_name(self, line_pos): + return line_pos == 0 and not self.first_statement_after_name_seen \ + and self.test_name_len < self.short_test_name_length + + +class ColumnWidthCounter(ModelTransformer): + + def __init__(self): + self.widths = [] + + def visit_Statement(self, statement): + if statement.type == Token.TESTCASE_HEADER: + self._count_widths_from_statement(statement) + elif statement.type != Token.TESTCASE_NAME: + self._count_widths_from_statement(statement, indent=1) + return statement + + def _count_widths_from_statement(self, statement, indent=0): + for line in statement.lines: + line = [t for t in line if t.type not in (Token.SEPARATOR, Token.EOL)] + for index, token in enumerate(line, start=indent): + if index >= len(self.widths): + self.widths.append(len(token.value)) + elif len(token.value) > self.widths[index]: + self.widths[index] = len(token.value) + + +class Aligner(ModelTransformer): + + def __init__(self, short_test_name_length, + setting_and_variable_name_length, pipes_mode): + self.short_test_name_length = short_test_name_length + self.setting_and_variable_name_length = \ + setting_and_variable_name_length + self.pipes_mode = pipes_mode + + def visit_TestCaseSection(self, section): + if len(section.header.data_tokens) > 1: + counter = ColumnWidthCounter() + counter.visit(section) + ColumnAligner(self.short_test_name_length, + counter.widths).visit(section) + return section + + def visit_KeywordSection(self, section): + return section + + def visit_Statement(self, statement): + for line in statement.lines: + value_tokens = [t for t in line if t.type + not in (Token.SEPARATOR, Token.EOL)] + if self._should_be_aligned(value_tokens): + first = value_tokens[0] + first.value = first.value.ljust( + self.setting_and_variable_name_length + ) + return statement + + def _should_be_aligned(self, tokens): + if not tokens: + return False + if len(tokens) == 1: + return self.pipes_mode + if len(tokens) == 2: + return tokens[0].type != Token.CONTINUATION or tokens[1].value + return True diff --git a/robot/lib/python3.8/site-packages/robot/utils/__init__.py b/robot/lib/python3.8/site-packages/robot/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2f908f90422e2d942336e0ec96571f0298621b79 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/__init__.py @@ -0,0 +1,82 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Various generic utility functions and classes. + +Utilities are mainly for internal usage, but external libraries and tools +may find some of them useful. Utilities are generally stable, but absolute +backwards compatibility between major versions is not guaranteed. + +All utilities are exposed via the :mod:`robot.utils` package, and should be +used either like:: + + from robot import utils + + assert utils.Matcher('H?llo').match('Hillo') + +or:: + + from robot.utils import Matcher + + assert Matcher('H?llo').match('Hillo') +""" + +from .argumentparser import ArgumentParser, cmdline2list +from .application import Application +from .compat import isatty, py2to3, StringIO, unwrap, with_metaclass +from .compress import compress_text +from .connectioncache import ConnectionCache +from .dotdict import DotDict +from .encoding import (CONSOLE_ENCODING, SYSTEM_ENCODING, console_decode, + console_encode, system_decode, system_encode) +from .error import (get_error_message, get_error_details, ErrorDetails) +from .escaping import escape, glob_escape, unescape, split_from_equals +from .etreewrapper import ET, ETSource +from .filereader import FileReader +from .frange import frange +from .markuputils import html_format, html_escape, xml_escape, attribute_escape +from .markupwriters import HtmlWriter, XmlWriter, NullMarkupWriter +from .importer import Importer +from .match import eq, Matcher, MultiMatcher +from .misc import (plural_or_not, printable_name, roundup, seq2str, + seq2str2) +from .normalizing import lower, normalize, normalize_whitespace, NormalizedDict +from .platform import (IRONPYTHON, JAVA_VERSION, JYTHON, PY_VERSION, + PY2, PY3, PYPY, UNIXY, WINDOWS, RERAISED_EXCEPTIONS) +from .recommendations import RecommendationFinder +from .robotenv import get_env_var, set_env_var, del_env_var, get_env_vars +from .robotinspect import is_java_init, is_java_method +from .robotio import (binary_file_writer, create_destination_directory, + file_writer) +from .robotpath import abspath, find_file, get_link_path, normpath +from .robottime import (elapsed_time_to_string, format_time, get_elapsed_time, + get_time, get_timestamp, secs_to_timestamp, + secs_to_timestr, timestamp_to_secs, timestr_to_secs, + parse_time) +from .robottypes import (FALSE_STRINGS, Mapping, MutableMapping, TRUE_STRINGS, + is_bytes, is_dict_like, is_falsy, is_integer, + is_list_like, is_number, is_pathlike, is_string, + is_truthy, is_unicode, type_name, unicode) +from .setter import setter, SetterAwareType +from .sortable import Sortable +from .text import (cut_long_message, format_assign_message, + get_console_length, getdoc, getshortdoc, pad_console_length, + rstrip, split_tags_from_doc, split_args_from_name_or_path) +from .unic import prepr, unic + + +def read_rest_data(rstfile): + from .restreader import read_rest_data + return read_rest_data(rstfile) diff --git a/robot/lib/python3.8/site-packages/robot/utils/application.py b/robot/lib/python3.8/site-packages/robot/utils/application.py new file mode 100644 index 0000000000000000000000000000000000000000..f328a72a526dfe98c56530d83384d4a174e6ecda --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/application.py @@ -0,0 +1,130 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import sys + +from robot.errors import (INFO_PRINTED, DATA_ERROR, STOPPED_BY_USER, + FRAMEWORK_ERROR, Information, DataError) + +from .argumentparser import ArgumentParser +from .encoding import console_encode +from .error import get_error_details + + +class Application(object): + + def __init__(self, usage, name=None, version=None, arg_limits=None, + env_options=None, logger=None, **auto_options): + self._ap = ArgumentParser(usage, name, version, arg_limits, + self.validate, env_options, **auto_options) + self._logger = logger or DefaultLogger() + + def main(self, arguments, **options): + raise NotImplementedError + + def validate(self, options, arguments): + return options, arguments + + def execute_cli(self, cli_arguments, exit=True): + with self._logger: + self._logger.info('%s %s' % (self._ap.name, self._ap.version)) + options, arguments = self._parse_arguments(cli_arguments) + rc = self._execute(arguments, options) + if exit: + self._exit(rc) + return rc + + def console(self, msg): + if msg: + print(console_encode(msg)) + + def _parse_arguments(self, cli_args): + try: + options, arguments = self.parse_arguments(cli_args) + except Information as msg: + self._report_info(msg.message) + except DataError as err: + self._report_error(err.message, help=True, exit=True) + else: + self._logger.info('Arguments: %s' % ','.join(arguments)) + return options, arguments + + def parse_arguments(self, cli_args): + """Public interface for parsing command line arguments. + + :param cli_args: Command line arguments as a list + :returns: options (dict), arguments (list) + :raises: :class:`~robot.errors.Information` when --help or --version used + :raises: :class:`~robot.errors.DataError` when parsing fails + """ + return self._ap.parse_args(cli_args) + + def execute(self, *arguments, **options): + with self._logger: + self._logger.info('%s %s' % (self._ap.name, self._ap.version)) + return self._execute(list(arguments), options) + + def _execute(self, arguments, options): + try: + rc = self.main(arguments, **options) + except DataError as err: + return self._report_error(err.message, help=True) + except (KeyboardInterrupt, SystemExit): + return self._report_error('Execution stopped by user.', + rc=STOPPED_BY_USER) + except: + error, details = get_error_details(exclude_robot_traces=False) + return self._report_error('Unexpected error: %s' % error, + details, rc=FRAMEWORK_ERROR) + else: + return rc or 0 + + def _report_info(self, message): + self.console(message) + self._exit(INFO_PRINTED) + + def _report_error(self, message, details=None, help=False, rc=DATA_ERROR, + exit=False): + if help: + message += '\n\nTry --help for usage information.' + if details: + message += '\n' + details + self._logger.error(message) + if exit: + self._exit(rc) + return rc + + def _exit(self, rc): + sys.exit(rc) + + +class DefaultLogger(object): + + def info(self, message): + pass + + def error(self, message): + print(console_encode(message)) + + def close(self): + pass + + def __enter__(self): + pass + + def __exit__(self, *exc_info): + pass diff --git a/robot/lib/python3.8/site-packages/robot/utils/argumentparser.py b/robot/lib/python3.8/site-packages/robot/utils/argumentparser.py new file mode 100644 index 0000000000000000000000000000000000000000..78e31b06ba6cb2c0afb047a015c10d0e4273828f --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/argumentparser.py @@ -0,0 +1,411 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import getopt # optparse was not supported by Jython 2.2 +import os +import re +import shlex +import sys +import glob +import string + +from robot.errors import DataError, Information, FrameworkError +from robot.version import get_full_version + +from .encoding import console_decode, system_decode +from .filereader import FileReader +from .misc import plural_or_not +from .platform import PY2 +from .robottypes import is_falsy, is_integer, is_string, is_unicode + + +def cmdline2list(args, escaping=False): + if PY2 and is_unicode(args): + args = args.encode('UTF-8') + decode = lambda item: item.decode('UTF-8') + else: + decode = lambda item: item + lexer = shlex.shlex(args, posix=True) + if is_falsy(escaping): + lexer.escape = '' + lexer.escapedquotes = '"\'' + lexer.commenters = '' + lexer.whitespace_split = True + try: + return [decode(token) for token in lexer] + except ValueError as err: + raise ValueError("Parsing '%s' failed: %s" % (args, err)) + + +class ArgumentParser(object): + _opt_line_re = re.compile(''' + ^\s{1,4} # 1-4 spaces in the beginning of the line + ((-\S\s)*) # all possible short options incl. spaces (group 1) + --(\S{2,}) # required long option (group 3) + (\s\S+)? # optional value (group 4) + (\s\*)? # optional '*' telling option allowed multiple times (group 5) + ''', re.VERBOSE) + + def __init__(self, usage, name=None, version=None, arg_limits=None, + validator=None, env_options=None, auto_help=True, + auto_version=True, auto_pythonpath=True, + auto_argumentfile=True): + """Available options and tool name are read from the usage. + + Tool name is got from the first row of the usage. It is either the + whole row or anything before first ' -- '. + """ + if not usage: + raise FrameworkError('Usage cannot be empty') + self.name = name or usage.splitlines()[0].split(' -- ')[0].strip() + self.version = version or get_full_version() + self._usage = usage + self._arg_limit_validator = ArgLimitValidator(arg_limits) + self._validator = validator + self._auto_help = auto_help + self._auto_version = auto_version + self._auto_pythonpath = auto_pythonpath + self._auto_argumentfile = auto_argumentfile + self._env_options = env_options + self._short_opts = '' + self._long_opts = [] + self._multi_opts = [] + self._flag_opts = [] + self._short_to_long = {} + self._expected_args = () + self._create_options(usage) + + def parse_args(self, args): + """Parse given arguments and return options and positional arguments. + + Arguments must be given as a list and are typically sys.argv[1:]. + + Options are returned as a dictionary where long options are keys. Value + is a string for those options that can be given only one time (if they + are given multiple times the last value is used) or None if the option + is not used at all. Value for options that can be given multiple times + (denoted with '*' in the usage) is a list which contains all the given + values and is empty if options are not used. Options not taken + arguments have value False when they are not set and True otherwise. + + Positional arguments are returned as a list in the order they are given. + + If 'check_args' is True, this method will automatically check that + correct number of arguments, as parsed from the usage line, are given. + If the last argument in the usage line ends with the character 's', + the maximum number of arguments is infinite. + + Possible errors in processing arguments are reported using DataError. + + Some options have a special meaning and are handled automatically + if defined in the usage and given from the command line: + + --argumentfile can be used to automatically read arguments from + a specified file. When --argumentfile is used, the parser always + allows using it multiple times. Adding '*' to denote that is thus + recommend. A special value 'stdin' can be used to read arguments from + stdin instead of a file. + + --pythonpath can be used to add extra path(s) to sys.path. + + --help and --version automatically generate help and version messages. + Version is generated based on the tool name and version -- see __init__ + for information how to set them. Help contains the whole usage given to + __init__. Possible text in the usage is replaced with the + given version. Both help and version are wrapped to Information + exception. + """ + args = self._get_env_options() + list(args) + args = [system_decode(a) for a in args] + if self._auto_argumentfile: + args = self._process_possible_argfile(args) + opts, args = self._parse_args(args) + if self._auto_argumentfile and opts.get('argumentfile'): + raise DataError("Using '--argumentfile' option in shortened format " + "like '--argumentf' is not supported.") + opts, args = self._handle_special_options(opts, args) + self._arg_limit_validator(args) + if self._validator: + opts, args = self._validator(opts, args) + return opts, args + + def _get_env_options(self): + if self._env_options: + options = os.getenv(self._env_options) + if options: + return cmdline2list(options) + return [] + + def _handle_special_options(self, opts, args): + if self._auto_help and opts.get('help'): + self._raise_help() + if self._auto_version and opts.get('version'): + self._raise_version() + if self._auto_pythonpath and opts.get('pythonpath'): + sys.path = self._get_pythonpath(opts['pythonpath']) + sys.path + for auto, opt in [(self._auto_help, 'help'), + (self._auto_version, 'version'), + (self._auto_pythonpath, 'pythonpath'), + (self._auto_argumentfile, 'argumentfile')]: + if auto and opt in opts: + opts.pop(opt) + return opts, args + + def _parse_args(self, args): + args = [self._lowercase_long_option(a) for a in args] + try: + opts, args = getopt.getopt(args, self._short_opts, self._long_opts) + except getopt.GetoptError as err: + raise DataError(err.msg) + return self._process_opts(opts), self._glob_args(args) + + def _lowercase_long_option(self, opt): + if not opt.startswith('--'): + return opt + if '=' not in opt: + return opt.lower() + opt, value = opt.split('=', 1) + return '%s=%s' % (opt.lower(), value) + + def _process_possible_argfile(self, args): + options = ['--argumentfile'] + for short_opt, long_opt in self._short_to_long.items(): + if long_opt == 'argumentfile': + options.append('-'+short_opt) + return ArgFileParser(options).process(args) + + def _process_opts(self, opt_tuple): + opts = self._get_default_opts() + for name, value in opt_tuple: + name = self._get_name(name) + if name in self._multi_opts: + opts[name].append(value) + elif name in self._flag_opts: + opts[name] = True + elif name.startswith('no') and name[2:] in self._flag_opts: + opts[name[2:]] = False + else: + opts[name] = value + return opts + + def _get_default_opts(self): + defaults = {} + for opt in self._long_opts: + opt = opt.rstrip('=') + if opt.startswith('no') and opt[2:] in self._flag_opts: + continue + defaults[opt] = [] if opt in self._multi_opts else None + return defaults + + def _glob_args(self, args): + temp = [] + for path in args: + paths = sorted(glob.glob(path)) + if paths: + temp.extend(paths) + else: + temp.append(path) + return temp + + def _get_name(self, name): + name = name.lstrip('-') + try: + return self._short_to_long[name] + except KeyError: + return name + + def _create_options(self, usage): + for line in usage.splitlines(): + res = self._opt_line_re.match(line) + if res: + self._create_option(short_opts=[o[1] for o in res.group(1).split()], + long_opt=res.group(3).lower(), + takes_arg=bool(res.group(4)), + is_multi=bool(res.group(5))) + + def _create_option(self, short_opts, long_opt, takes_arg, is_multi): + self._verify_long_not_already_used(long_opt, not takes_arg) + for sopt in short_opts: + if sopt in self._short_to_long: + self._raise_option_multiple_times_in_usage('-' + sopt) + self._short_to_long[sopt] = long_opt + if is_multi: + self._multi_opts.append(long_opt) + if takes_arg: + long_opt += '=' + short_opts = [sopt+':' for sopt in short_opts] + else: + if long_opt.startswith('no'): + long_opt = long_opt[2:] + self._long_opts.append('no' + long_opt) + self._flag_opts.append(long_opt) + self._long_opts.append(long_opt) + self._short_opts += (''.join(short_opts)) + + def _verify_long_not_already_used(self, opt, flag=False): + if flag: + if opt.startswith('no'): + opt = opt[2:] + self._verify_long_not_already_used(opt) + self._verify_long_not_already_used('no' + opt) + elif opt in [o.rstrip('=') for o in self._long_opts]: + self._raise_option_multiple_times_in_usage('--' + opt) + + def _get_pythonpath(self, paths): + if is_string(paths): + paths = [paths] + temp = [] + for path in self._split_pythonpath(paths): + temp.extend(glob.glob(path)) + return [os.path.abspath(path) for path in temp if path] + + def _split_pythonpath(self, paths): + # paths may already contain ':' as separator + tokens = ':'.join(paths).split(':') + if os.sep == '/': + return tokens + # Fix paths split like 'c:\temp' -> 'c', '\temp' + ret = [] + drive = '' + for item in tokens: + item = item.replace('/', '\\') + if drive and item.startswith('\\'): + ret.append('%s:%s' % (drive, item)) + drive = '' + continue + if drive: + ret.append(drive) + drive = '' + if len(item) == 1 and item in string.ascii_letters: + drive = item + else: + ret.append(item) + if drive: + ret.append(drive) + return ret + + def _raise_help(self): + usage = self._usage + if self.version: + usage = usage.replace('', self.version) + raise Information(usage) + + def _raise_version(self): + raise Information('%s %s' % (self.name, self.version)) + + def _raise_option_multiple_times_in_usage(self, opt): + raise FrameworkError("Option '%s' multiple times in usage" % opt) + + +class ArgLimitValidator(object): + + def __init__(self, arg_limits): + self._min_args, self._max_args = self._parse_arg_limits(arg_limits) + + def _parse_arg_limits(self, arg_limits): + if arg_limits is None: + return 0, sys.maxsize + if is_integer(arg_limits): + return arg_limits, arg_limits + if len(arg_limits) == 1: + return arg_limits[0], sys.maxsize + return arg_limits[0], arg_limits[1] + + def __call__(self, args): + if not (self._min_args <= len(args) <= self._max_args): + self._raise_invalid_args(self._min_args, self._max_args, len(args)) + + def _raise_invalid_args(self, min_args, max_args, arg_count): + min_end = plural_or_not(min_args) + if min_args == max_args: + expectation = "%d argument%s" % (min_args, min_end) + elif max_args != sys.maxsize: + expectation = "%d to %d arguments" % (min_args, max_args) + else: + expectation = "at least %d argument%s" % (min_args, min_end) + raise DataError("Expected %s, got %d." % (expectation, arg_count)) + + +class ArgFileParser(object): + + def __init__(self, options): + self._options = options + + def process(self, args): + while True: + path, replace = self._get_index(args) + if not path: + break + args[replace] = self._get_args(path) + return args + + def _get_index(self, args): + for opt in self._options: + start = opt + '=' if opt.startswith('--') else opt + for index, arg in enumerate(args): + normalized_arg = arg.lower() if opt.startswith('--') else arg + # Handles `--argumentfile foo` and `-A foo` + if normalized_arg == opt and index + 1 < len(args): + return args[index+1], slice(index, index+2) + # Handles `--argumentfile=foo` and `-Afoo` + if normalized_arg.startswith(start): + return arg[len(start):], slice(index, index+1) + return None, -1 + + def _get_args(self, path): + if path.upper() != 'STDIN': + content = self._read_from_file(path) + else: + content = self._read_from_stdin() + return self._process_file(content) + + def _read_from_file(self, path): + try: + with FileReader(path) as reader: + return reader.read() + except (IOError, UnicodeError) as err: + raise DataError("Opening argument file '%s' failed: %s" + % (path, err)) + + def _read_from_stdin(self): + return console_decode(sys.__stdin__.read()) + + def _process_file(self, content): + args = [] + for line in content.splitlines(): + line = line.strip() + if line.startswith('-'): + args.extend(self._split_option(line)) + elif line and not line.startswith('#'): + args.append(line) + return args + + def _split_option(self, line): + separator = self._get_option_separator(line) + if not separator: + return [line] + option, value = line.split(separator, 1) + if separator == ' ': + value = value.strip() + return [option, value] + + def _get_option_separator(self, line): + if ' ' not in line and '=' not in line: + return None + if '=' not in line: + return ' ' + if ' ' not in line: + return '=' + return ' ' if line.index(' ') < line.index('=') else '=' diff --git a/robot/lib/python3.8/site-packages/robot/utils/asserts.py b/robot/lib/python3.8/site-packages/robot/utils/asserts.py new file mode 100644 index 0000000000000000000000000000000000000000..cfa65da9f64c714e747512e6c6e41c9b3e8998ee --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/asserts.py @@ -0,0 +1,241 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Convenience functions for testing both in unit and higher levels. + +Benefits: + - Integrates 100% with unittest (see example below) + - Can be easily used without unittest (using unittest.TestCase when you + only need convenient asserts is not so nice) + - Saved typing and shorter lines because no need to have 'self.' before + asserts. These are static functions after all so that is OK. + - All 'equals' methods (by default) report given values even if optional + message given. This behavior can be controlled with the optional values + argument. + +Drawbacks: + - unittest is not able to filter as much non-interesting traceback away + as with its own methods because AssertionErrors occur outside. + +Most of the functions are copied more or less directly from unittest.TestCase +which comes with the following license. Further information about unittest in +general can be found from http://pyunit.sourceforge.net/. This module can be +used freely in same terms as unittest. + +unittest license:: + + Copyright (c) 1999-2003 Steve Purcell + This module is free software, and you may redistribute it and/or modify + it under the same terms as Python itself, so long as this copyright message + and disclaimer are retained in their original form. + + IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, + SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF + THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + DAMAGE. + + THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, + AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, + SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + +Examples:: + + import unittest + from robot.utils.asserts import assert_equal + + class MyTests(unittest.TestCase): + + def test_old_style(self): + self.assertEqual(1, 2, 'my msg') + + def test_new_style(self): + assert_equal(1, 2, 'my msg') + +Example output:: + + FF + ====================================================================== + FAIL: test_old_style (example.MyTests) + ---------------------------------------------------------------------- + Traceback (most recent call last): + File "example.py", line 7, in test_old_style + self.assertEqual(1, 2, 'my msg') + AssertionError: my msg + + ====================================================================== + FAIL: test_new_style (example.MyTests) + ---------------------------------------------------------------------- + Traceback (most recent call last): + File "example.py", line 10, in test_new_style + assert_equal(1, 2, 'my msg') + File "/path/to/robot/utils/asserts.py", line 181, in assert_equal + _report_inequality_failure(first, second, msg, values, '!=') + File "/path/to/robot/utils/asserts.py", line 229, in _report_inequality_failure + raise AssertionError(msg) + AssertionError: my msg: 1 != 2 + + ---------------------------------------------------------------------- + Ran 2 tests in 0.000s + + FAILED (failures=2) +""" + +from .robottypes import type_name +from .unic import unic + + +def fail(msg=None): + """Fail test immediately with the given message.""" + _report_failure(msg) + + +def assert_false(expr, msg=None): + """Fail the test if the expression is True.""" + if expr: + _report_failure(msg) + + +def assert_true(expr, msg=None): + """Fail the test unless the expression is True.""" + if not expr: + _report_failure(msg) + + +def assert_not_none(obj, msg=None, values=True): + """Fail the test if given object is None.""" + _msg = 'is None' + if obj is None: + if msg is None: + msg = _msg + elif values is True: + msg = '%s: %s' % (msg, _msg) + _report_failure(msg) + + +def assert_none(obj, msg=None, values=True): + """Fail the test if given object is not None.""" + _msg = '%r is not None' % obj + if obj is not None: + if msg is None: + msg = _msg + elif values is True: + msg = '%s: %s' % (msg, _msg) + _report_failure(msg) + + +def assert_raises(exc_class, callable_obj, *args, **kwargs): + """Fail unless an exception of class exc_class is thrown by callable_obj. + + callable_obj is invoked with arguments args and keyword arguments + kwargs. If a different type of exception is thrown, it will not be + caught, and the test case will be deemed to have suffered an + error, exactly as for an unexpected exception. + + If a correct exception is raised, the exception instance is returned + by this method. + """ + try: + callable_obj(*args, **kwargs) + except exc_class as err: + return err + else: + if hasattr(exc_class,'__name__'): + exc_name = exc_class.__name__ + else: + exc_name = str(exc_class) + _report_failure('%s not raised' % exc_name) + + +def assert_raises_with_msg(exc_class, expected_msg, callable_obj, *args, + **kwargs): + """Similar to fail_unless_raises but also checks the exception message.""" + try: + callable_obj(*args, **kwargs) + except exc_class as err: + assert_equal(expected_msg, unic(err), + 'Correct exception but wrong message') + else: + if hasattr(exc_class,'__name__'): + exc_name = exc_class.__name__ + else: + exc_name = str(exc_class) + _report_failure('%s not raised' % exc_name) + + +def assert_equal(first, second, msg=None, values=True, formatter=None): + """Fail if given objects are unequal as determined by the '==' operator.""" + if not first == second: + _report_inequality(first, second, '!=', msg, values, formatter) + + +def assert_not_equal(first, second, msg=None, values=True, formatter=None): + """Fail if given objects are equal as determined by the '==' operator.""" + if first == second: + _report_inequality(first, second, '==', msg, values, formatter) + + +def assert_almost_equal(first, second, places=7, msg=None, values=True): + """Fail if the two objects are unequal after rounded to given places. + + inequality is determined by object's difference rounded to the + given number of decimal places (default 7) and comparing to zero. + Note that decimal places (from zero) are usually not the same as + significant digits (measured from the most significant digit). + """ + if round(second - first, places) != 0: + extra = 'within %r places' % places + _report_inequality(first, second, '!=', msg, values, extra=extra) + + +def assert_not_almost_equal(first, second, places=7, msg=None, values=True): + """Fail if the two objects are unequal after rounded to given places. + + Equality is determined by object's difference rounded to to the + given number of decimal places (default 7) and comparing to zero. + Note that decimal places (from zero) are usually not the same as + significant digits (measured from the most significant digit). + """ + if round(second-first, places) == 0: + extra = 'within %r places' % places + _report_inequality(first, second, '==', msg, values, extra=extra) + + +def _report_failure(msg): + if msg is None: + raise AssertionError() + raise AssertionError(msg) + + +def _report_inequality(obj1, obj2, delim, msg=None, values=False, + formatter=None, extra=None): + if not msg: + msg = _format_message(obj1, obj2, delim, formatter) + elif values: + msg = '%s: %s' % (msg, _format_message(obj1, obj2, delim, formatter)) + if values and extra: + msg += ' ' + extra + raise AssertionError(msg) + + +def _format_message(obj1, obj2, delim, formatter=None): + formatter = formatter or unic + str1 = formatter(obj1) + str2 = formatter(obj2) + if delim == '!=' and str1 == str2: + return '%s (%s) != %s (%s)' % (str1, type_name(obj1), + str2, type_name(obj2)) + return '%s %s %s' % (str1, delim, str2) diff --git a/robot/lib/python3.8/site-packages/robot/utils/charwidth.py b/robot/lib/python3.8/site-packages/robot/utils/charwidth.py new file mode 100644 index 0000000000000000000000000000000000000000..d207b7b0ec22c6473710f70595f09aa8f6096d6d --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/charwidth.py @@ -0,0 +1,139 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A module to handle different character widths on the console. + +Some East Asian characters have width of two on console, and combining +characters themselves take no extra space. + +See issue 604 [1] for more details about East Asian characters. The issue also +contains `generate_wild_chars.py` script that was originally used to create +`_EAST_ASIAN_WILD_CHARS` mapping. An updated version of the script is attached +to issue 1096. Big thanks for xieyanbo for the script and the original patch. + +Note that Python's `unicodedata` module is not used here because importing +it takes several seconds on Jython. + +[1] https://github.com/robotframework/robotframework/issues/604 +[2] https://github.com/robotframework/robotframework/issues/1096 +""" + +def get_char_width(char): + char = ord(char) + if _char_in_map(char, _COMBINING_CHARS): + return 0 + if _char_in_map(char, _EAST_ASIAN_WILD_CHARS): + return 2 + return 1 + +def _char_in_map(char, map): + for begin, end in map: + if char < begin: + break + if begin <= char <= end: + return True + return False + + +_COMBINING_CHARS = [(768, 879)] + +_EAST_ASIAN_WILD_CHARS = [ + (888, 889), (895, 899), (907, 907), (909, 909), (930, 930), + (1316, 1328), (1367, 1368), (1376, 1376), (1416, 1416), + (1419, 1424), (1480, 1487), (1515, 1519), (1525, 1535), + (1540, 1541), (1564, 1565), (1568, 1568), (1631, 1631), + (1806, 1806), (1867, 1868), (1970, 1983), (2043, 2304), + (2362, 2363), (2382, 2383), (2389, 2391), (2419, 2426), + (2432, 2432), (2436, 2436), (2445, 2446), (2449, 2450), + (2473, 2473), (2481, 2481), (2483, 2485), (2490, 2491), + (2501, 2502), (2505, 2506), (2511, 2518), (2520, 2523), + (2526, 2526), (2532, 2533), (2555, 2560), (2564, 2564), + (2571, 2574), (2577, 2578), (2601, 2601), (2609, 2609), + (2612, 2612), (2615, 2615), (2618, 2619), (2621, 2621), + (2627, 2630), (2633, 2634), (2638, 2640), (2642, 2648), + (2653, 2653), (2655, 2661), (2678, 2688), (2692, 2692), + (2702, 2702), (2706, 2706), (2729, 2729), (2737, 2737), + (2740, 2740), (2746, 2747), (2758, 2758), (2762, 2762), + (2766, 2767), (2769, 2783), (2788, 2789), (2800, 2800), + (2802, 2816), (2820, 2820), (2829, 2830), (2833, 2834), + (2857, 2857), (2865, 2865), (2868, 2868), (2874, 2875), + (2885, 2886), (2889, 2890), (2894, 2901), (2904, 2907), + (2910, 2910), (2916, 2917), (2930, 2945), (2948, 2948), + (2955, 2957), (2961, 2961), (2966, 2968), (2971, 2971), + (2973, 2973), (2976, 2978), (2981, 2983), (2987, 2989), + (3002, 3005), (3011, 3013), (3017, 3017), (3022, 3023), + (3025, 3030), (3032, 3045), (3067, 3072), (3076, 3076), + (3085, 3085), (3089, 3089), (3113, 3113), (3124, 3124), + (3130, 3132), (3141, 3141), (3145, 3145), (3150, 3156), + (3159, 3159), (3162, 3167), (3172, 3173), (3184, 3191), + (3200, 3201), (3204, 3204), (3213, 3213), (3217, 3217), + (3241, 3241), (3252, 3252), (3258, 3259), (3269, 3269), + (3273, 3273), (3278, 3284), (3287, 3293), (3295, 3295), + (3300, 3301), (3312, 3312), (3315, 3329), (3332, 3332), + (3341, 3341), (3345, 3345), (3369, 3369), (3386, 3388), + (3397, 3397), (3401, 3401), (3406, 3414), (3416, 3423), + (3428, 3429), (3446, 3448), (3456, 3457), (3460, 3460), + (3479, 3481), (3506, 3506), (3516, 3516), (3518, 3519), + (3527, 3529), (3531, 3534), (3541, 3541), (3543, 3543), + (3552, 3569), (3573, 3584), (3643, 3646), (3676, 3712), + (3715, 3715), (3717, 3718), (3721, 3721), (3723, 3724), + (3726, 3731), (3736, 3736), (3744, 3744), (3748, 3748), + (3750, 3750), (3752, 3753), (3756, 3756), (3770, 3770), + (3774, 3775), (3781, 3781), (3783, 3783), (3790, 3791), + (3802, 3803), (3806, 3839), (3912, 3912), (3949, 3952), + (3980, 3983), (3992, 3992), (4029, 4029), (4045, 4045), + (4053, 4095), (4250, 4253), (4294, 4303), (4349, 4447), + (4515, 4519), (4602, 4607), (4681, 4681), (4686, 4687), + (4695, 4695), (4697, 4697), (4702, 4703), (4745, 4745), + (4750, 4751), (4785, 4785), (4790, 4791), (4799, 4799), + (4801, 4801), (4806, 4807), (4823, 4823), (4881, 4881), + (4886, 4887), (4955, 4958), (4989, 4991), (5018, 5023), + (5109, 5120), (5751, 5759), (5789, 5791), (5873, 5887), + (5901, 5901), (5909, 5919), (5943, 5951), (5972, 5983), + (5997, 5997), (6001, 6001), (6004, 6015), (6110, 6111), + (6122, 6127), (6138, 6143), (6159, 6159), (6170, 6175), + (6264, 6271), (6315, 6399), (6429, 6431), (6444, 6447), + (6460, 6463), (6465, 6467), (6510, 6511), (6517, 6527), + (6570, 6575), (6602, 6607), (6618, 6621), (6684, 6685), + (6688, 6911), (6988, 6991), (7037, 7039), (7083, 7085), + (7098, 7167), (7224, 7226), (7242, 7244), (7296, 7423), + (7655, 7677), (7958, 7959), (7966, 7967), (8006, 8007), + (8014, 8015), (8024, 8024), (8026, 8026), (8028, 8028), + (8030, 8030), (8062, 8063), (8117, 8117), (8133, 8133), + (8148, 8149), (8156, 8156), (8176, 8177), (8181, 8181), + (8191, 8191), (8293, 8297), (8306, 8307), (8335, 8335), + (8341, 8351), (8374, 8399), (8433, 8447), (8528, 8530), + (8585, 8591), (9001, 9002), (9192, 9215), (9255, 9279), + (9291, 9311), (9886, 9887), (9917, 9919), (9924, 9984), + (9989, 9989), (9994, 9995), (10024, 10024), (10060, 10060), + (10062, 10062), (10067, 10069), (10071, 10071), (10079, 10080), + (10133, 10135), (10160, 10160), (10175, 10175), (10187, 10187), + (10189, 10191), (11085, 11087), (11093, 11263), (11311, 11311), + (11359, 11359), (11376, 11376), (11390, 11391), (11499, 11512), + (11558, 11567), (11622, 11630), (11632, 11647), (11671, 11679), + (11687, 11687), (11695, 11695), (11703, 11703), (11711, 11711), + (11719, 11719), (11727, 11727), (11735, 11735), (11743, 11743), + (11825, 12350), (12352, 19903), (19968, 42239), (42540, 42559), + (42592, 42593), (42612, 42619), (42648, 42751), (42893, 43002), + (43052, 43071), (43128, 43135), (43205, 43213), (43226, 43263), + (43348, 43358), (43360, 43519), (43575, 43583), (43598, 43599), + (43610, 43611), (43616, 55295), (63744, 64255), (64263, 64274), + (64280, 64284), (64311, 64311), (64317, 64317), (64319, 64319), + (64322, 64322), (64325, 64325), (64434, 64466), (64832, 64847), + (64912, 64913), (64968, 65007), (65022, 65023), (65040, 65055), + (65063, 65135), (65141, 65141), (65277, 65278), (65280, 65376), + (65471, 65473), (65480, 65481), (65488, 65489), (65496, 65497), + (65501, 65511), (65519, 65528), (65534, 65535), + ] diff --git a/robot/lib/python3.8/site-packages/robot/utils/compat.py b/robot/lib/python3.8/site-packages/robot/utils/compat.py new file mode 100644 index 0000000000000000000000000000000000000000..d6f2e23faf7a3ca02adb80b862db91229d96cc3e --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/compat.py @@ -0,0 +1,86 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from .platform import IRONPYTHON, PY2 + + +if PY2: + # io.StringIO only accepts u'foo' with Python 2. + from StringIO import StringIO + + + def py2to3(cls): + if hasattr(cls, '__unicode__'): + cls.__str__ = lambda self: unicode(self).encode('UTF-8') + return cls + + + def unwrap(func): + return func + +else: + from inspect import unwrap + from io import StringIO + + + def py2to3(cls): + if hasattr(cls, '__unicode__'): + cls.__str__ = lambda self: self.__unicode__() + if hasattr(cls, '__nonzero__'): + cls.__bool__ = lambda self: self.__nonzero__() + return cls + + +# Copied from Jinja2, released under the BSD license. +# https://github.com/mitsuhiko/jinja2/blob/743598d788528921df825479d64f492ef60bef82/jinja2/_compat.py#L88 +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a + # dummy metaclass for one level of class instantiation that replaces + # itself with the actual metaclass. + class metaclass(type): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +# On IronPython sys.stdxxx.isatty() always returns True +if not IRONPYTHON: + + def isatty(stream): + # first check if buffer was detached + if hasattr(stream, 'buffer') and stream.buffer is None: + return False + if not hasattr(stream, 'isatty'): + return False + try: + return stream.isatty() + except ValueError: # Occurs if file is closed. + return False + +else: + + from ctypes import windll + + _HANDLE_IDS = {sys.__stdout__ : -11, sys.__stderr__ : -12} + _CONSOLE_TYPE = 2 + + def isatty(stream): + if stream not in _HANDLE_IDS: + return False + handle = windll.kernel32.GetStdHandle(_HANDLE_IDS[stream]) + return windll.kernel32.GetFileType(handle) == _CONSOLE_TYPE diff --git a/robot/lib/python3.8/site-packages/robot/utils/compress.py b/robot/lib/python3.8/site-packages/robot/utils/compress.py new file mode 100644 index 0000000000000000000000000000000000000000..ee6fb89a1d2faa073831d5eecaae09a1366b9138 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/compress.py @@ -0,0 +1,53 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 + +from .platform import JYTHON, PY2 + + +def compress_text(text): + result = base64.b64encode(_compress(text.encode('UTF-8'))) + return result if PY2 else result.decode('ASCII') + + +if not JYTHON: + + import zlib + + def _compress(text): + return zlib.compress(text, 9) + +else: + + # Custom compress implementation was originally used to avoid memory leak + # (http://bugs.jython.org/issue1775). Kept around still because it is a bit + # faster than Jython's standard zlib.compress. + + from java.util.zip import Deflater + import jarray + + _DEFLATOR = Deflater(9, False) + + def _compress(text): + _DEFLATOR.setInput(text) + _DEFLATOR.finish() + buf = jarray.zeros(1024, 'b') + compressed = [] + while not _DEFLATOR.finished(): + length = _DEFLATOR.deflate(buf, 0, 1024) + compressed.append(buf[:length].tostring()) + _DEFLATOR.reset() + return ''.join(compressed) diff --git a/robot/lib/python3.8/site-packages/robot/utils/connectioncache.py b/robot/lib/python3.8/site-packages/robot/utils/connectioncache.py new file mode 100644 index 0000000000000000000000000000000000000000..6b4d8d47ae9045105ff2e78d398f5f8e9e27714a --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/connectioncache.py @@ -0,0 +1,186 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import warnings + +from .compat import py2to3 +from .normalizing import NormalizedDict +from .robottypes import is_string + + +@py2to3 +class ConnectionCache(object): + """Cache for test libs to use with concurrent connections, processes, etc. + + The cache stores the registered connections (or other objects) and allows + switching between them using generated indices or user given aliases. + This is useful with any test library where there's need for multiple + concurrent connections, processes, etc. + + This class can, and is, used also outside the core framework by SSHLibrary, + Selenium(2)Library, etc. Backwards compatibility is thus important when + doing changes. + """ + + def __init__(self, no_current_msg='No open connection.'): + self._no_current = NoConnection(no_current_msg) + self.current = self._no_current #: Current active connection. + self._connections = [] + self._aliases = NormalizedDict() + + @property + def current_index(self): + if not self: + return None + for index, conn in enumerate(self): + if conn is self.current: + return index + 1 + + @current_index.setter + def current_index(self, index): + self.current = self._connections[index - 1] \ + if index is not None else self._no_current + + def register(self, connection, alias=None): + """Registers given connection with optional alias and returns its index. + + Given connection is set to be the :attr:`current` connection. + + If alias is given, it must be a string. Aliases are case and space + insensitive. + + The index of the first connection after initialization, and after + :meth:`close_all` or :meth:`empty_cache`, is 1, second is 2, etc. + """ + self.current = connection + self._connections.append(connection) + index = len(self._connections) + if is_string(alias): + self._aliases[alias] = index + return index + + def switch(self, alias_or_index): + """Switches to the connection specified by the given alias or index. + + Updates :attr:`current` and also returns its new value. + + Alias is whatever was given to :meth:`register` method and indices + are returned by it. Index can be given either as an integer or + as a string that can be converted to an integer. Raises an error + if no connection with the given index or alias found. + """ + self.current = self.get_connection(alias_or_index) + return self.current + + def get_connection(self, alias_or_index=None): + """Get the connection specified by the given alias or index.. + + If ``alias_or_index`` is ``None``, returns the current connection + if it is active, or raises an error if it is not. + + Alias is whatever was given to :meth:`register` method and indices + are returned by it. Index can be given either as an integer or + as a string that can be converted to an integer. Raises an error + if no connection with the given index or alias found. + """ + if alias_or_index is None: + if not self: + self.current.raise_error() + return self.current + try: + index = self.resolve_alias_or_index(alias_or_index) + except ValueError as err: + raise RuntimeError(err.args[0]) + return self._connections[index-1] + + __getitem__ = get_connection + + def close_all(self, closer_method='close'): + """Closes connections using given closer method and empties cache. + + If simply calling the closer method is not adequate for closing + connections, clients should close connections themselves and use + :meth:`empty_cache` afterwards. + """ + for conn in self._connections: + getattr(conn, closer_method)() + self.empty_cache() + return self.current + + def empty_cache(self): + """Empties the connection cache. + + Indexes of the new connections starts from 1 after this. + """ + self.current = self._no_current + self._connections = [] + self._aliases = NormalizedDict() + + def __iter__(self): + return iter(self._connections) + + def __len__(self): + return len(self._connections) + + def __nonzero__(self): + return self.current is not self._no_current + + def resolve_alias_or_index(self, alias_or_index): + for resolver in self._resolve_alias, self._resolve_index: + try: + return resolver(alias_or_index) + except ValueError: + pass + raise ValueError("Non-existing index or alias '%s'." % alias_or_index) + + def _resolve_alias_or_index(self, alias_or_index): + # TODO: Remove this function for good in RF 3.3. + # See https://github.com/robotframework/robotframework/issues/3125 + warnings.warn("'ConnectionCache._resolve_alias_or_index' is " + "deprecated. Use 'resolve_alias_or_index' instead.", + UserWarning) + return self.resolve_alias_or_index(alias_or_index) + + def _resolve_alias(self, alias): + if is_string(alias) and alias in self._aliases: + return self._aliases[alias] + raise ValueError + + def _resolve_index(self, index): + try: + index = int(index) + except TypeError: + raise ValueError + if not 0 < index <= len(self._connections): + raise ValueError + return index + + +@py2to3 +class NoConnection(object): + + def __init__(self, message): + self.message = message + + def __getattr__(self, name): + if name.startswith('__') and name.endswith('__'): + raise AttributeError + self.raise_error() + + def raise_error(self): + raise RuntimeError(self.message) + + def __nonzero__(self): + return False diff --git a/robot/lib/python3.8/site-packages/robot/utils/dotdict.py b/robot/lib/python3.8/site-packages/robot/utils/dotdict.py new file mode 100644 index 0000000000000000000000000000000000000000..cbb77005fd0f3ce12e55613e334e2104acacb96d --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/dotdict.py @@ -0,0 +1,70 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import OrderedDict + +from .robottypes import is_dict_like + + +class DotDict(OrderedDict): + + def __init__(self, *args, **kwds): + args = [self._convert_nested_initial_dicts(a) for a in args] + kwds = self._convert_nested_initial_dicts(kwds) + OrderedDict.__init__(self, *args, **kwds) + + def _convert_nested_initial_dicts(self, value): + items = value.items() if is_dict_like(value) else value + return OrderedDict((key, self._convert_nested_dicts(value)) + for key, value in items) + + def _convert_nested_dicts(self, value): + if isinstance(value, DotDict): + return value + if is_dict_like(value): + return DotDict(value) + if isinstance(value, list): + value[:] = [self._convert_nested_dicts(item) for item in value] + return value + + def __getattr__(self, key): + try: + return self[key] + except KeyError: + raise AttributeError(key) + + def __setattr__(self, key, value): + if not key.startswith('_OrderedDict__'): + self[key] = value + else: + OrderedDict.__setattr__(self, key, value) + + def __delattr__(self, key): + try: + self.pop(key) + except KeyError: + OrderedDict.__delattr__(self, key) + + def __eq__(self, other): + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other + + def __str__(self): + return '{%s}' % ', '.join('%r: %r' % (key, self[key]) for key in self) + + # Must use original dict.__repr__ to allow customising PrettyPrinter. + __repr__ = dict.__repr__ diff --git a/robot/lib/python3.8/site-packages/robot/utils/encoding.py b/robot/lib/python3.8/site-packages/robot/utils/encoding.py new file mode 100644 index 0000000000000000000000000000000000000000..fa17b3cd4dab064e89e5a14a7e49afdf1635efd1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/encoding.py @@ -0,0 +1,115 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys + +from .encodingsniffer import get_console_encoding, get_system_encoding +from .compat import isatty +from .platform import JYTHON, IRONPYTHON, PY3, PY_VERSION +from .robottypes import is_unicode +from .unic import unic + + +CONSOLE_ENCODING = get_console_encoding() +SYSTEM_ENCODING = get_system_encoding() +PYTHONIOENCODING = os.getenv('PYTHONIOENCODING') + + +def console_decode(string, encoding=CONSOLE_ENCODING, force=False): + """Decodes bytes from console encoding to Unicode. + + By default uses the system console encoding, but that can be configured + using the `encoding` argument. In addition to the normal encodings, + it is possible to use case-insensitive values `CONSOLE` and `SYSTEM` to + use the system console and system encoding, respectively. + + By default returns Unicode strings as-is. The `force` argument can be used + on IronPython where all strings are `unicode` and caller knows decoding + is needed. + """ + if is_unicode(string) and not (IRONPYTHON and force): + return string + encoding = {'CONSOLE': CONSOLE_ENCODING, + 'SYSTEM': SYSTEM_ENCODING}.get(encoding.upper(), encoding) + try: + return string.decode(encoding) + except UnicodeError: + return unic(string) + + +def console_encode(string, errors='replace', stream=sys.__stdout__): + """Encodes Unicode to bytes in console or system encoding. + + Determines the encoding to use based on the given stream and system + configuration. On Python 3 and IronPython returns Unicode, otherwise + returns bytes. + """ + encoding = _get_console_encoding(stream) + if PY3 and encoding != 'UTF-8': + return string.encode(encoding, errors).decode(encoding) + if PY3 or IRONPYTHON: + return string + return string.encode(encoding, errors) + + +def _get_console_encoding(stream): + encoding = getattr(stream, 'encoding', None) + if isatty(stream): + return encoding or CONSOLE_ENCODING + if PYTHONIOENCODING: + return PYTHONIOENCODING + # Jython and IronPython have wrong encoding if outputs are redirected. + if encoding and not (JYTHON or IRONPYTHON): + return encoding + return SYSTEM_ENCODING + + +# These interpreters handle communication with system APIs using Unicode. +if PY3 or IRONPYTHON or (JYTHON and PY_VERSION < (2, 7, 1)): + + def system_decode(string): + return string if is_unicode(string) else unic(string) + + def system_encode(string, errors='replace'): + return string if is_unicode(string) else unic(string) + +else: + + # Jython 2.7.1+ uses UTF-8 with cli args etc. regardless the actual system + # encoding. Cannot set the "real" SYSTEM_ENCODING to that value because + # we use it also for other purposes. + _SYSTEM_ENCODING = SYSTEM_ENCODING if not JYTHON else 'UTF-8' + + def system_decode(string): + """Decodes bytes from system (e.g. cli args or env vars) to Unicode. + + Depending on the usage, at least cli args may already be Unicode. + """ + if is_unicode(string): + return string + try: + return string.decode(_SYSTEM_ENCODING) + except UnicodeError: + return unic(string) + + def system_encode(string, errors='replace'): + """Encodes Unicode to system encoding (e.g. cli args and env vars). + + Non-Unicode values are first converted to Unicode. + """ + if not is_unicode(string): + string = unic(string) + return string.encode(_SYSTEM_ENCODING, errors) diff --git a/robot/lib/python3.8/site-packages/robot/utils/encodingsniffer.py b/robot/lib/python3.8/site-packages/robot/utils/encodingsniffer.py new file mode 100644 index 0000000000000000000000000000000000000000..54c91d7a09addc358e2c82d78494a40468a74a8e --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/encodingsniffer.py @@ -0,0 +1,126 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import locale + +from .compat import isatty +from .platform import JYTHON, PY2, PY3, PY_VERSION, UNIXY, WINDOWS + + +if UNIXY: + DEFAULT_CONSOLE_ENCODING = 'UTF-8' + DEFAULT_SYSTEM_ENCODING = 'UTF-8' +else: + DEFAULT_CONSOLE_ENCODING = 'cp437' + DEFAULT_SYSTEM_ENCODING = 'cp1252' + + +def get_system_encoding(): + platform_getters = [(True, _get_python_system_encoding), + (JYTHON, _get_java_system_encoding), + (UNIXY, _get_unixy_encoding), + (WINDOWS, _get_windows_system_encoding)] + return _get_encoding(platform_getters, DEFAULT_SYSTEM_ENCODING) + + +def get_console_encoding(): + platform_getters = [(True, _get_stream_output_encoding), + (UNIXY, _get_unixy_encoding), + (WINDOWS, _get_windows_console_encoding)] + return _get_encoding(platform_getters, DEFAULT_CONSOLE_ENCODING) + + +def _get_encoding(platform_getters, default): + for platform, getter in platform_getters: + if platform: + encoding = getter() + if _is_valid(encoding): + return encoding + return default + + +def _get_python_system_encoding(): + # `locale.getpreferredencoding(False)` should return exactly what we want, + # but it doesn't seem to work outside Windows on Python 2. Luckily on these + # platforms `sys.getfilesystemencoding()` seems to do the right thing. + # Jython 2.7.1+ actually uses UTF-8 regardless the system encoding, but + # that's handled by `system_decode/encode` utilities separately. + if PY2 and not WINDOWS: + return sys.getfilesystemencoding() + return locale.getpreferredencoding(False) + + +def _get_java_system_encoding(): + # This is only used with Jython 2.7.0, others get encoding already + # from `_get_python_system_encoding`. + from java.lang import System + return System.getProperty('file.encoding') + + +def _get_unixy_encoding(): + # Cannot use `locale.getdefaultlocale()` because it raises ValueError + # if encoding is invalid. Using same environment variables here anyway. + # https://docs.python.org/3/library/locale.html#locale.getdefaultlocale + for name in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE': + if name in os.environ: + # Encoding can be in format like `UTF-8` or `en_US.UTF-8` + encoding = os.environ[name].split('.')[-1] + if _is_valid(encoding): + return encoding + return None + + +def _get_stream_output_encoding(): + # Python 3.6+ uses UTF-8 as encoding with output streams. + # We want the real console encoding regardless the platform. + if WINDOWS and PY_VERSION >= (3, 6): + return None + for stream in sys.__stdout__, sys.__stderr__, sys.__stdin__: + if isatty(stream): + encoding = getattr(stream, 'encoding', None) + if _is_valid(encoding): + return encoding + return None + + +def _get_windows_system_encoding(): + return _get_code_page('GetACP') + + +def _get_windows_console_encoding(): + return _get_code_page('GetConsoleOutputCP') + + +def _get_code_page(method_name): + from ctypes import cdll + try: + method = getattr(cdll.kernel32, method_name) + except TypeError: # Occurred few times with IronPython on CI. + return None + method.argtypes = () # Needed with Jython. + return 'cp%s' % method() + + +def _is_valid(encoding): + if not encoding: + return False + try: + 'test'.encode(encoding) + except LookupError: + return False + else: + return True diff --git a/robot/lib/python3.8/site-packages/robot/utils/error.py b/robot/lib/python3.8/site-packages/robot/utils/error.py new file mode 100644 index 0000000000000000000000000000000000000000..3fc5fd8b59fac8b27f0db99ccd9422f57cd0673d --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/error.py @@ -0,0 +1,232 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re +import sys +import traceback + +from robot.errors import RobotError + +from .encoding import system_decode +from .platform import JYTHON, PY3, PY_VERSION, RERAISED_EXCEPTIONS +from .unic import unic + + +EXCLUDE_ROBOT_TRACES = not os.getenv('ROBOT_INTERNAL_TRACES') +if JYTHON: + from java.io import StringWriter, PrintWriter + from java.lang import Throwable, OutOfMemoryError +else: + Throwable = () + + +def get_error_message(): + """Returns error message of the last occurred exception. + + This method handles also exceptions containing unicode messages. Thus it + MUST be used to get messages from all exceptions originating outside the + framework. + """ + return ErrorDetails().message + + +def get_error_details(exclude_robot_traces=EXCLUDE_ROBOT_TRACES): + """Returns error message and details of the last occurred exception.""" + details = ErrorDetails(exclude_robot_traces=exclude_robot_traces) + return details.message, details.traceback + + +def ErrorDetails(exc_info=None, exclude_robot_traces=EXCLUDE_ROBOT_TRACES): + """This factory returns an object that wraps the last occurred exception + + It has attributes `message`, `traceback` and `error`, where `message` + contains type and message of the original error, `traceback` contains the + traceback/stack trace and `error` contains the original error instance. + """ + exc_type, exc_value, exc_traceback = exc_info or sys.exc_info() + if exc_type in RERAISED_EXCEPTIONS: + raise exc_value + details = PythonErrorDetails \ + if not isinstance(exc_value, Throwable) else JavaErrorDetails + return details(exc_type, exc_value, exc_traceback, exclude_robot_traces) + + +class _ErrorDetails(object): + _generic_exception_names = ('AssertionError', 'AssertionFailedError', + 'Exception', 'Error', 'RuntimeError', + 'RuntimeException') + + def __init__(self, exc_type, exc_value, exc_traceback, + exclude_robot_traces=True): + self.error = exc_value + self._exc_type = exc_type + self._exc_traceback = exc_traceback + self._exclude_robot_traces = exclude_robot_traces + self._message = None + self._traceback = None + + @property + def message(self): + if self._message is None: + self._message = self._get_message() + return self._message + + def _get_message(self): + raise NotImplementedError + + @property + def traceback(self): + if self._traceback is None: + self._traceback = self._get_details() + return self._traceback + + def _get_details(self): + raise NotImplementedError + + def _get_name(self, exc_type): + try: + return exc_type.__name__ + except AttributeError: + return unic(exc_type) + + def _format_message(self, name, message): + message = unic(message or '') + message = self._clean_up_message(message, name) + name = name.split('.')[-1] # Use only last part of the name + if not message: + return name + if self._is_generic_exception(name): + return message + return '%s: %s' % (name, message) + + def _is_generic_exception(self, name): + return (name in self._generic_exception_names or + isinstance(self.error, RobotError) or + getattr(self.error, 'ROBOT_SUPPRESS_NAME', False)) + + def _clean_up_message(self, message, name): + return message + + +class PythonErrorDetails(_ErrorDetails): + + def _get_message(self): + name = self._get_name(self._exc_type) + return self._format_message(name, unic(self.error)) + + def _get_details(self): + if isinstance(self.error, RobotError): + return self.error.details + return 'Traceback (most recent call last):\n' + self._get_traceback() + + def _get_traceback(self): + tb = self._exc_traceback + while tb and self._is_excluded_traceback(tb): + tb = tb.tb_next + if not tb: + return ' None' + if PY3: + # Everything is Unicode so we can simply use `format_tb`. + formatted = traceback.format_tb(tb) + else: + # Entries are bytes and may even have different encoding. + entries = [self._decode_entry(e) for e in traceback.extract_tb(tb)] + formatted = traceback.format_list(entries) + return ''.join(formatted).rstrip() + + def _is_excluded_traceback(self, traceback): + if not self._exclude_robot_traces: + return False + module = traceback.tb_frame.f_globals.get('__name__') + return module and module.startswith('robot.') + + def _decode_entry(self, traceback_entry): + path, lineno, func, text = traceback_entry + # Traceback entries in Python 2 use bytes using different encodings. + # path: system encoding (except on Jython 2.7.0 where it's latin1) + # line: integer + # func: always ASCII on Python 2 + # text: depends on source encoding; UTF-8 is an ASCII compatible guess + buggy_jython = JYTHON and PY_VERSION < (2, 7, 1) + if not buggy_jython: + path = system_decode(path) + else: + path = path.decode('latin1', 'replace') + if text is not None: + text = text.decode('UTF-8', 'replace') + return path, lineno, func, text + + +class JavaErrorDetails(_ErrorDetails): + _java_trace_re = re.compile('^\s+at (\w.+)') + _ignored_java_trace = ('org.python.', 'robot.running.', 'robot$py.', + 'sun.reflect.', 'java.lang.reflect.') + + def _get_message(self): + exc_name = self._get_name(self._exc_type) + # OOME.getMessage and even toString seem to throw NullPointerException + if not self._is_out_of_memory_error(self._exc_type): + exc_msg = self.error.getMessage() + else: + exc_msg = str(self.error) + return self._format_message(exc_name, exc_msg) + + def _is_out_of_memory_error(self, exc_type): + return exc_type is OutOfMemoryError + + def _get_details(self): + # OOME.printStackTrace seems to throw NullPointerException + if self._is_out_of_memory_error(self._exc_type): + return '' + output = StringWriter() + self.error.printStackTrace(PrintWriter(output)) + details = '\n'.join(line for line in output.toString().splitlines() + if not self._is_ignored_stack_trace_line(line)) + msg = unic(self.error.getMessage() or '') + if msg: + details = details.replace(msg, '', 1) + return details + + def _is_ignored_stack_trace_line(self, line): + if not line: + return True + res = self._java_trace_re.match(line) + if res is None: + return False + location = res.group(1) + for entry in self._ignored_java_trace: + if location.startswith(entry): + return True + return False + + def _clean_up_message(self, msg, name): + msg = self._remove_stack_trace_lines(msg) + return self._remove_exception_name(msg, name).strip() + + def _remove_stack_trace_lines(self, msg): + lines = msg.splitlines() + while lines: + if self._java_trace_re.match(lines[-1]): + lines.pop() + else: + break + return '\n'.join(lines) + + def _remove_exception_name(self, msg, name): + tokens = msg.split(':', 1) + if len(tokens) == 2 and tokens[0] == name: + msg = tokens[1] + return msg diff --git a/robot/lib/python3.8/site-packages/robot/utils/escaping.py b/robot/lib/python3.8/site-packages/robot/utils/escaping.py new file mode 100644 index 0000000000000000000000000000000000000000..51b4a509c6ad69149f0e1bcca721eb366fa2a690 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/escaping.py @@ -0,0 +1,145 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +from .platform import PY3 +from .robottypes import is_string + + +if PY3: + unichr = chr + +_CONTROL_WORDS = frozenset(('ELSE', 'ELSE IF', 'AND', 'WITH NAME')) +_SEQUENCES_TO_BE_ESCAPED = ('\\', '${', '@{', '%{', '&{', '*{', '=') + + +def escape(item): + if not is_string(item): + return item + if item in _CONTROL_WORDS: + return '\\' + item + for seq in _SEQUENCES_TO_BE_ESCAPED: + if seq in item: + item = item.replace(seq, '\\' + seq) + return item + + +def glob_escape(item): + # Python 3.4+ has `glob.escape()` but it has special handling for drives + # that we don't want. + for char in '[*?': + if char in item: + item = item.replace(char, '[%s]' % char) + return item + + +class Unescaper(object): + _escape_sequences = re.compile(r''' + (\\+) # escapes + (n\ ?|r|t # n, r, or t + |x[0-9a-fA-F]{2} # x+HH + |u[0-9a-fA-F]{4} # u+HHHH + |U[0-9a-fA-F]{8} # U+HHHHHHHH + )? # optionally + ''', re.VERBOSE) + + def __init__(self): + self._escape_handlers = { + '': lambda value: value, + 'n': self._handle_n, + 'r': lambda value: '\r', + 't': lambda value: '\t', + 'x': self._hex_to_unichr, + 'u': self._hex_to_unichr, + 'U': self._hex_to_unichr + } + + def _handle_n(self, value): + if value: + # TODO: Remove this feature for good in RF 3.3! + from robot.output import librarylogger + librarylogger.warn( + "Ignoring space after '\\n' is deprecated. For more info see: " + "https://github.com/robotframework/robotframework/issues/3333" + ) + return '\n' + + def _hex_to_unichr(self, value): + ordinal = int(value, 16) + # No Unicode code points above 0x10FFFF + if ordinal > 0x10FFFF: + return 'U' + value + # unichr only supports ordinals up to 0xFFFF with narrow Python builds + if ordinal > 0xFFFF: + return eval(r"u'\U%08x'" % ordinal) + return unichr(ordinal) + + def unescape(self, item): + if not (is_string(item) and '\\' in item): + return item + return self._escape_sequences.sub(self._handle_escapes, item) + + def _handle_escapes(self, match): + escapes, text = match.groups() + half, is_escaped = divmod(len(escapes), 2) + escapes = escapes[:half] + text = text or '' + if is_escaped: + marker, value = text[:1], text[1:] + text = self._escape_handlers[marker](value) + return escapes + text + + +unescape = Unescaper().unescape + + +def split_from_equals(string): + from robot.variables import VariableIterator + if not is_string(string) or '=' not in string: + return string, None + variables = VariableIterator(string, ignore_errors=True) + if not variables and '\\' not in string: + return tuple(string.split('=', 1)) + try: + index = _find_split_index(string, variables) + except ValueError: + return string, None + return string[:index], string[index+1:] + + +def _find_split_index(string, variables): + relative_index = 0 + for before, match, string in variables: + try: + return _find_split_index_from_part(before) + relative_index + except ValueError: + relative_index += len(before) + len(match) + return _find_split_index_from_part(string) + relative_index + + +def _find_split_index_from_part(string): + index = 0 + while '=' in string[index:]: + index += string[index:].index('=') + if _not_escaping(string[:index]): + return index + index += 1 + raise ValueError + + +def _not_escaping(name): + backslashes = len(name) - len(name.rstrip('\\')) + return backslashes % 2 == 0 diff --git a/robot/lib/python3.8/site-packages/robot/utils/etreewrapper.py b/robot/lib/python3.8/site-packages/robot/utils/etreewrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..233a5bbf3e4f2f5e00bc7658292cd03150d036c7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/etreewrapper.py @@ -0,0 +1,115 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from io import BytesIO +import re + +from .compat import py2to3 +from .platform import IRONPYTHON, PY_VERSION, PY3 +from .robottypes import is_bytes, is_pathlike, is_string + +if PY3: + from os import fsdecode +else: + from .encoding import console_decode as fsdecode + + +IRONPYTHON_WITH_BROKEN_ETREE = IRONPYTHON and PY_VERSION < (2, 7, 9) +NO_ETREE_ERROR = 'No valid ElementTree XML parser module found' + + +if not IRONPYTHON_WITH_BROKEN_ETREE: + try: + from xml.etree import cElementTree as ET + except ImportError: + try: + from xml.etree import ElementTree as ET + except ImportError: + raise ImportError(NO_ETREE_ERROR) +else: + # Standard ElementTree works only with IronPython 2.7.9+ + # https://github.com/IronLanguages/ironpython2/issues/370 + try: + from elementtree import ElementTree as ET + except ImportError: + raise ImportError(NO_ETREE_ERROR) + from StringIO import StringIO + + +# cElementTree.VERSION seems to always be 1.0.6. We want real API version. +if ET.VERSION < '1.3' and hasattr(ET, 'tostringlist'): + ET.VERSION = '1.3' + + +@py2to3 +class ETSource(object): + + def __init__(self, source): + # ET on Python < 3.6 doesn't support pathlib.Path + if PY_VERSION < (3, 6) and is_pathlike(source): + source = str(source) + self._source = source + self._opened = None + + def __enter__(self): + self._opened = self._open_if_necessary(self._source) + return self._opened or self._source + + def _open_if_necessary(self, source): + if self._is_path(source) or self._is_already_open(source): + return None + if IRONPYTHON_WITH_BROKEN_ETREE: + return StringIO(source) + if is_bytes(source): + return BytesIO(source) + encoding = self._find_encoding(source) + return BytesIO(source.encode(encoding)) + + def _is_path(self, source): + if is_pathlike(source): + return True + elif is_string(source): + prefix = '<' + elif is_bytes(source): + prefix = b'<' + else: + return False + return not source.lstrip().startswith(prefix) + + def _is_already_open(self, source): + return not (is_string(source) or is_bytes(source)) + + def _find_encoding(self, source): + match = re.match("\s*<\?xml .*encoding=(['\"])(.*?)\\1.*\?>", source) + return match.group(2) if match else 'UTF-8' + + def __exit__(self, exc_type, exc_value, exc_trace): + if self._opened: + self._opened.close() + + def __unicode__(self): + source = self._source + if self._is_path(source): + return self._path_to_string(source) + if hasattr(source, 'name'): + return self._path_to_string(source.name) + return u'' + + def _path_to_string(self, path): + if is_pathlike(path): + return str(path) + if is_bytes(path): + return fsdecode(path) + return path diff --git a/robot/lib/python3.8/site-packages/robot/utils/filereader.py b/robot/lib/python3.8/site-packages/robot/utils/filereader.py new file mode 100644 index 0000000000000000000000000000000000000000..94adf28b9583e0050e38cbf9baf664b1152fdaf6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/filereader.py @@ -0,0 +1,107 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os.path + +from .compat import StringIO +from .platform import IRONPYTHON +from .robottypes import is_bytes, is_pathlike, is_string + + +class FileReader(object): + """Utility to ease reading different kind of files. + + Supports different sources where to read the data: + + - The source can be a path to a file, either as a string or as a + ``pathlib.Path`` instance in Python 3. The file itself must be + UTF-8 encoded. + + - Alternatively the source can be an already opened file object, + including a StringIO or BytesIO object. The file can contain either + Unicode text or UTF-8 encoded bytes. + + - The third options is giving the source as Unicode text directly. + This requires setting ``accept_text=True`` when creating the reader. + + In all cases bytes are automatically decoded to Unicode and possible + BOM removed. + """ + + def __init__(self, source, accept_text=False): + self.file, self.name, self._opened = self._get_file(source, accept_text) + + def _get_file(self, source, accept_text): + path = self._get_path(source, accept_text) + if path: + try: + file = open(path, 'rb') + except ValueError: + # Converting ValueError to IOError needed due to this IPY bug: + # https://github.com/IronLanguages/ironpython2/issues/700 + raise IOError("Invalid path '%s'." % path) + opened = True + elif is_string(source): + file = StringIO(source) + opened = True + else: + file = source + opened = False + name = getattr(file, 'name', '') + return file, name, opened + + def _get_path(self, source, accept_text): + if is_pathlike(source): + return str(source) + if not is_string(source): + return None + if not accept_text: + return source + if '\n' in source: + return None + if os.path.isabs(source) or os.path.exists(source): + return source + return None + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + if self._opened: + self.file.close() + + def read(self): + return self._decode(self.file.read()) + + def readlines(self): + first_line = True + for line in self.file.readlines(): + yield self._decode(line, remove_bom=first_line) + first_line = False + + def _decode(self, content, remove_bom=True): + force_decode = IRONPYTHON and self._is_binary_file() + if is_bytes(content) or force_decode: + content = content.decode('UTF-8') + if remove_bom and content.startswith(u'\ufeff'): + content = content[1:] + if '\r\n' in content: + content = content.replace('\r\n', '\n') + return content + + def _is_binary_file(self): + mode = getattr(self.file, 'mode', '') + encoding = getattr(self.file, 'encoding', 'ascii').lower() + return 'r' in mode and encoding == 'ascii' diff --git a/robot/lib/python3.8/site-packages/robot/utils/frange.py b/robot/lib/python3.8/site-packages/robot/utils/frange.py new file mode 100644 index 0000000000000000000000000000000000000000..98f22f36883bdbfc0caa0292452a276e61a477d9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/frange.py @@ -0,0 +1,63 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .misc import roundup +from .robottypes import is_integer, is_string + + +def frange(*args): + """Like ``range()`` but accepts float arguments.""" + if all(is_integer(arg) for arg in args): + return list(range(*args)) + start, stop, step = _get_start_stop_step(args) + digits = max(_digits(start), _digits(stop), _digits(step)) + factor = pow(10, digits) + return [x/float(factor) for x in range(roundup(start*factor), + roundup(stop*factor), + roundup(step*factor))] + + +def _get_start_stop_step(args): + if len(args) == 1: + return 0, args[0], 1 + if len(args) == 2: + return args[0], args[1], 1 + if len(args) == 3: + return args + raise TypeError('frange expected 1-3 arguments, got %d.' % len(args)) + + +def _digits(number): + if not is_string(number): + number = repr(number) + if 'e' in number: + return _digits_with_exponent(number) + if '.' in number: + return _digits_with_fractional(number) + return 0 + + +def _digits_with_exponent(number): + mantissa, exponent = number.split('e') + mantissa_digits = _digits(mantissa) + exponent_digits = int(exponent) * -1 + return max(mantissa_digits + exponent_digits, 0) + + +def _digits_with_fractional(number): + fractional = number.split('.')[1] + if fractional == '0': + return 0 + return len(fractional) diff --git a/robot/lib/python3.8/site-packages/robot/utils/htmlformatters.py b/robot/lib/python3.8/site-packages/robot/utils/htmlformatters.py new file mode 100644 index 0000000000000000000000000000000000000000..091f5d2985c4c4a8fa1eb2363b7436be0e786031 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/htmlformatters.py @@ -0,0 +1,305 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +from functools import partial +from itertools import cycle + + +class LinkFormatter(object): + _image_exts = ('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg') + _link = re.compile(r'\[(.+?\|.*?)\]') + _url = re.compile(r''' +((^|\ ) ["'(\[{]*) # begin of line or space and opt. any char "'([{ +([a-z][\w+-.]*://[^\s|]+?) # url +(?=[)\]}"'.,!?:;|]* ($|\ )) # opt. any char )]}"'.,!?:;| and eol or space +''', re.VERBOSE|re.MULTILINE|re.IGNORECASE) + + def format_url(self, text): + return self._format_url(text, format_as_image=False) + + def _format_url(self, text, format_as_image=True): + if '://' not in text: + return text + return self._url.sub(partial(self._replace_url, format_as_image), text) + + def _replace_url(self, format_as_image, match): + pre = match.group(1) + url = match.group(3) + if format_as_image and self._is_image(url): + return pre + self._get_image(url) + return pre + self._get_link(url) + + def _get_image(self, src, title=None): + return '' \ + % (self._quot(src), self._quot(title or src)) + + def _get_link(self, href, content=None): + return '%s' % (self._quot(href), content or href) + + def _quot(self, attr): + return attr if '"' not in attr else attr.replace('"', '"') + + def format_link(self, text): + # 2nd, 4th, etc. token contains link, others surrounding content + tokens = self._link.split(text) + formatters = cycle((self._format_url, self._format_link)) + return ''.join(f(t) for f, t in zip(formatters, tokens)) + + def _format_link(self, text): + link, content = [t.strip() for t in text.split('|', 1)] + if self._is_image(content): + content = self._get_image(content, link) + elif self._is_image(link): + return self._get_image(link, content) + return self._get_link(link, content) + + def _is_image(self, text): + + return (text.startswith('data:image/') + or text.lower().endswith(self._image_exts)) + + +class LineFormatter(object): + handles = lambda self, line: True + newline = '\n' + _bold = re.compile(''' +( # prefix (group 1) + (^|\ ) # begin of line or space + ["'(]* _? # optionally any char "'( and optional begin of italic +) # +\* # start of bold +([^\ ].*?) # no space and then anything (group 3) +\* # end of bold +(?= # start of postfix (non-capturing group) + _? ["').,!?:;]* # optional end of italic and any char "').,!?:; + ($|\ ) # end of line or space +) +''', re.VERBOSE) + _italic = re.compile(''' +( (^|\ ) ["'(]* ) # begin of line or space and opt. any char "'( +_ # start of italic +([^\ _].*?) # no space or underline and then anything +_ # end of italic +(?= ["').,!?:;]* ($|\ ) ) # opt. any char "').,!?:; and end of line or space +''', re.VERBOSE) + _code = re.compile(''' +( (^|\ ) ["'(]* ) # same as above with _ changed to `` +`` +([^\ `].*?) +`` +(?= ["').,!?:;]* ($|\ ) ) +''', re.VERBOSE) + + def __init__(self): + self._formatters = [('*', self._format_bold), + ('_', self._format_italic), + ('``', self._format_code), + ('', LinkFormatter().format_link)] + + def format(self, line): + for marker, formatter in self._formatters: + if marker in line: + line = formatter(line) + return line + + def _format_bold(self, line): + return self._bold.sub('\\1\\3', line) + + def _format_italic(self, line): + return self._italic.sub('\\1\\3', line) + + def _format_code(self, line): + return self._code.sub('\\1\\3', line) + + +class HtmlFormatter(object): + + def __init__(self): + self._results = [] + self._formatters = [TableFormatter(), + PreformattedFormatter(), + ListFormatter(), + HeaderFormatter(), + RulerFormatter()] + self._formatters.append(ParagraphFormatter(self._formatters[:])) + self._current = None + + def format(self, text): + for line in text.splitlines(): + self._process_line(line) + self._end_current() + return '\n'.join(self._results) + + def _process_line(self, line): + if not line.strip(): + self._end_current() + elif self._current and self._current.handles(line): + self._current.add(line) + else: + self._end_current() + self._current = self._find_formatter(line) + self._current.add(line) + + def _end_current(self): + if self._current: + self._results.append(self._current.end()) + self._current = None + + def _find_formatter(self, line): + for formatter in self._formatters: + if formatter.handles(line): + return formatter + + +class _Formatter(object): + _strip_lines = True + + def __init__(self): + self._lines = [] + + def handles(self, line): + return self._handles(line.strip() if self._strip_lines else line) + + def _handles(self, line): + raise NotImplementedError + + def add(self, line): + self._lines.append(line.strip() if self._strip_lines else line) + + def end(self): + result = self.format(self._lines) + self._lines = [] + return result + + def format(self, lines): + raise NotImplementedError + + +class _SingleLineFormatter(_Formatter): + + def _handles(self, line): + return not self._lines and self.match(line) + + def match(self, line): + raise NotImplementedError + + def format(self, lines): + return self.format_line(lines[0]) + + def format_line(self, line): + raise NotImplementedError + + +class RulerFormatter(_SingleLineFormatter): + match = re.compile('^-{3,}$').match + + def format_line(self, line): + return '
' + + +class HeaderFormatter(_SingleLineFormatter): + match = re.compile(r'^(={1,3})\s+(\S.*?)\s+\1$').match + + def format_line(self, line): + level, text = self.match(line).groups() + level = len(level) + 1 + return '%s' % (level, text, level) + + +class ParagraphFormatter(_Formatter): + _format_line = LineFormatter().format + + def __init__(self, other_formatters): + _Formatter.__init__(self) + self._other_formatters = other_formatters + + def _handles(self, line): + return not any(other.handles(line) + for other in self._other_formatters) + + def format(self, lines): + return '

%s

' % self._format_line(' '.join(lines)) + + +class TableFormatter(_Formatter): + _table_line = re.compile('^\| (.* |)\|$') + _line_splitter = re.compile(' \|(?= )') + _format_cell_content = LineFormatter().format + + def _handles(self, line): + return self._table_line.match(line) is not None + + def format(self, lines): + return self._format_table([self._split_to_cells(l) for l in lines]) + + def _split_to_cells(self, line): + return [cell.strip() for cell in self._line_splitter.split(line[1:-1])] + + def _format_table(self, rows): + maxlen = max(len(row) for row in rows) + table = ['
'] + for row in rows: + row += [''] * (maxlen - len(row)) # fix ragged tables + table.append('') + table.extend(self._format_cell(cell) for cell in row) + table.append('') + table.append('
') + return '\n'.join(table) + + def _format_cell(self, content): + if content.startswith('=') and content.endswith('='): + tx = 'th' + content = content[1:-1].strip() + else: + tx = 'td' + return '<%s>%s' % (tx, self._format_cell_content(content), tx) + + +class PreformattedFormatter(_Formatter): + _format_line = LineFormatter().format + + def _handles(self, line): + return line.startswith('| ') or line == '|' + + def format(self, lines): + lines = [self._format_line(line[2:]) for line in lines] + return '\n'.join(['
'] + lines + ['
']) + + +class ListFormatter(_Formatter): + _strip_lines = False + _format_item = LineFormatter().format + + def _handles(self, line): + return line.strip().startswith('- ') or \ + line.startswith(' ') and self._lines + + def format(self, lines): + items = ['
  • %s
  • ' % self._format_item(line) + for line in self._combine_lines(lines)] + return '\n'.join(['
      '] + items + ['
    ']) + + def _combine_lines(self, lines): + current = [] + for line in lines: + line = line.strip() + if not line.startswith('- '): + current.append(line) + continue + if current: + yield ' '.join(current) + current = [line[2:].strip()] + yield ' '.join(current) diff --git a/robot/lib/python3.8/site-packages/robot/utils/importer.py b/robot/lib/python3.8/site-packages/robot/utils/importer.py new file mode 100644 index 0000000000000000000000000000000000000000..8534c08002ae383b0e2a8dbdef384b402a060dc2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/importer.py @@ -0,0 +1,300 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import inspect + +from robot.errors import DataError + +from .encoding import system_decode, system_encode +from .error import get_error_details +from .platform import JYTHON, IRONPYTHON, PY2, PY3, PYPY +from .robotpath import abspath, normpath +from .robottypes import type_name, is_unicode + +if PY3: + from importlib import invalidate_caches as invalidate_import_caches +else: + invalidate_import_caches = lambda: None +if JYTHON: + from java.lang.System import getProperty + + +class Importer(object): + + def __init__(self, type=None, logger=None): + if not logger: + from robot.output import LOGGER as logger + self._type = type or '' + self._logger = logger + self._importers = (ByPathImporter(logger), + NonDottedImporter(logger), + DottedImporter(logger)) + self._by_path_importer = self._importers[0] + + def import_class_or_module(self, name, instantiate_with_args=None, + return_source=False): + """Imports Python class/module or Java class with given name. + + Class can either live in a module/package or be standalone Java class. + In the former case the name is something like 'MyClass' and in the + latter it could be 'your.package.YourLibrary'. Python classes always + live in a module, but if the module name is exactly same as the class + name then simple 'MyLibrary' will import a class. + + Python modules can be imported both using format 'MyModule' and + 'mymodule.submodule'. + + `name` can also be a path to the imported file/directory. In that case + importing is done using `import_class_or_module_by_path` method. + + If `instantiate_with_args` is not None, imported classes are + instantiated with the specified arguments automatically. + """ + try: + imported, source = self._import_class_or_module(name) + self._log_import_succeeded(imported, name, source) + imported = self._instantiate_if_needed(imported, instantiate_with_args) + except DataError as err: + self._raise_import_failed(name, err) + else: + return self._handle_return_values(imported, source, return_source) + + def _import_class_or_module(self, name): + for importer in self._importers: + if importer.handles(name): + return importer.import_(name) + + def _handle_return_values(self, imported, source, return_source=False): + if not return_source: + return imported + if source and os.path.exists(source): + source = self._sanitize_source(source) + return imported, source + + def _sanitize_source(self, source): + source = normpath(source) + if os.path.isdir(source): + candidate = os.path.join(source, '__init__.py') + elif source.endswith('.pyc'): + candidate = source[:-4] + '.py' + elif source.endswith('$py.class'): + candidate = source[:-9] + '.py' + elif source.endswith('.class'): + candidate = source[:-6] + '.java' + else: + return source + return candidate if os.path.exists(candidate) else source + + def import_class_or_module_by_path(self, path, instantiate_with_args=None): + """Import a Python module or Java class using a file system path. + + When importing a Python file, the path must end with '.py' and the + actual file must also exist. When importing Java classes, the path + must end with '.java' or '.class'. The class file must exist in both + cases and in the former case also the source file must exist. + + If `instantiate_with_args` is not None, imported classes are + instantiated with the specified arguments automatically. + """ + try: + imported, source = self._by_path_importer.import_(path) + self._log_import_succeeded(imported, imported.__name__, source) + return self._instantiate_if_needed(imported, instantiate_with_args) + except DataError as err: + self._raise_import_failed(path, err) + + def _raise_import_failed(self, name, error): + import_type = '%s ' % self._type if self._type else '' + msg = "Importing %s'%s' failed: %s" % (import_type, name, error.message) + if not error.details: + raise DataError(msg) + msg = [msg, error.details] + msg.extend(self._get_items_in('PYTHONPATH', sys.path)) + if JYTHON: + classpath = getProperty('java.class.path').split(os.path.pathsep) + msg.extend(self._get_items_in('CLASSPATH', classpath)) + raise DataError('\n'.join(msg)) + + def _get_items_in(self, type, items): + yield '%s:' % type + for item in items: + if item: + yield ' %s' % (item if is_unicode(item) + else system_decode(item)) + + def _instantiate_if_needed(self, imported, args): + if args is None: + return imported + if inspect.isclass(imported): + return self._instantiate_class(imported, args) + if args: + raise DataError("Modules do not take arguments.") + return imported + + def _instantiate_class(self, imported, args): + try: + return imported(*args) + except: + raise DataError('Creating instance failed: %s\n%s' % get_error_details()) + + def _log_import_succeeded(self, item, name, source): + import_type = '%s ' % self._type if self._type else '' + item_type = 'module' if inspect.ismodule(item) else 'class' + location = ("'%s'" % source) if source else 'unknown location' + self._logger.info("Imported %s%s '%s' from %s." + % (import_type, item_type, name, location)) + + +class _Importer(object): + + def __init__(self, logger): + self._logger = logger + + def _import(self, name, fromlist=None, retry=True): + if name in sys.builtin_module_names: + raise DataError('Cannot import custom module with same name as ' + 'Python built-in module.') + invalidate_import_caches() + try: + try: + return __import__(name, fromlist=fromlist) + except ImportError: + # Hack to support standalone Jython. For more information, see: + # https://github.com/robotframework/robotframework/issues/515 + # http://bugs.jython.org/issue1778514 + if JYTHON and fromlist and retry: + __import__('%s.%s' % (name, fromlist[0])) + return self._import(name, fromlist, retry=False) + # IronPython loses traceback when using plain raise. + # https://github.com/IronLanguages/main/issues/989 + if IRONPYTHON: + exec('raise sys.exc_type, sys.exc_value, sys.exc_traceback') + raise + except: + raise DataError(*get_error_details()) + + def _verify_type(self, imported): + if inspect.isclass(imported) or inspect.ismodule(imported): + return imported + raise DataError('Expected class or module, got %s.' + % type_name(imported)) + + def _get_class_from_module(self, module, name=None): + klass = getattr(module, name or module.__name__, None) + return klass if inspect.isclass(klass) else None + + def _get_source(self, imported): + try: + source = inspect.getfile(imported) + except TypeError: + return None + return abspath(source) if source else None + + +class ByPathImporter(_Importer): + _valid_import_extensions = ('.py', '.java', '.class', '') + + def handles(self, path): + return os.path.isabs(path) + + def import_(self, path): + self._verify_import_path(path) + self._remove_wrong_module_from_sys_modules(path) + module = self._import_by_path(path) + imported = self._get_class_from_module(module) or module + return self._verify_type(imported), path + + def _verify_import_path(self, path): + if not os.path.exists(path): + raise DataError('File or directory does not exist.') + if not os.path.isabs(path): + raise DataError('Import path must be absolute.') + if not os.path.splitext(path)[1] in self._valid_import_extensions: + raise DataError('Not a valid file or directory to import.') + + def _remove_wrong_module_from_sys_modules(self, path): + importing_from, name = self._split_path_to_module(path) + importing_package = os.path.splitext(path)[1] == '' + if self._wrong_module_imported(name, importing_from, importing_package): + del sys.modules[name] + self._logger.info("Removed module '%s' from sys.modules to import " + "fresh module." % name) + + def _split_path_to_module(self, path): + module_dir, module_file = os.path.split(abspath(path)) + module_name = os.path.splitext(module_file)[0] + if module_name.endswith('$py'): + module_name = module_name[:-3] + return module_dir, module_name + + def _wrong_module_imported(self, name, importing_from, importing_package): + if name not in sys.modules: + return False + source = getattr(sys.modules[name], '__file__', None) + if not source: # play safe (occurs at least with java based modules) + return True + imported_from, imported_package = self._get_import_information(source) + return (normpath(importing_from, case_normalize=True) != + normpath(imported_from, case_normalize=True) or + importing_package != imported_package) + + def _get_import_information(self, source): + imported_from, imported_file = self._split_path_to_module(source) + imported_package = imported_file == '__init__' + if imported_package: + imported_from = os.path.dirname(imported_from) + return imported_from, imported_package + + def _import_by_path(self, path): + module_dir, module_name = self._split_path_to_module(path) + # Other interpreters work also with Unicode paths. + # https://bitbucket.org/pypy/pypy/issues/3112 + if PYPY and PY2: + module_dir = system_encode(module_dir) + sys.path.insert(0, module_dir) + try: + return self._import(module_name) + finally: + sys.path.remove(module_dir) + + +class NonDottedImporter(_Importer): + + def handles(self, name): + return '.' not in name + + def import_(self, name): + module = self._import(name) + imported = self._get_class_from_module(module) or module + return self._verify_type(imported), self._get_source(imported) + + +class DottedImporter(_Importer): + + def handles(self, name): + return '.' in name + + def import_(self, name): + parent_name, lib_name = name.rsplit('.', 1) + parent = self._import(parent_name, fromlist=[str(lib_name)]) + try: + imported = getattr(parent, lib_name) + except AttributeError: + raise DataError("Module '%s' does not contain '%s'." + % (parent_name, lib_name)) + imported = self._get_class_from_module(imported, lib_name) or imported + return self._verify_type(imported), self._get_source(imported) diff --git a/robot/lib/python3.8/site-packages/robot/utils/markuputils.py b/robot/lib/python3.8/site-packages/robot/utils/markuputils.py new file mode 100644 index 0000000000000000000000000000000000000000..c503f246aecf1890382c3db4478ad223ee476090 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/markuputils.py @@ -0,0 +1,52 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +from .htmlformatters import LinkFormatter, HtmlFormatter + + +_format_url = LinkFormatter().format_url +_generic_escapes = (('&', '&'), ('<', '<'), ('>', '>')) +_attribute_escapes = _generic_escapes \ + + (('"', '"'), ('\n', ' '), ('\r', ' '), ('\t', ' ')) +_illegal_chars_in_xml = re.compile(u'[\x00-\x08\x0B\x0C\x0E-\x1F\uFFFE\uFFFF]') + + +def html_escape(text, linkify=True): + text = _escape(text) + if linkify and '://' in text: + text = _format_url(text) + return text + + +def xml_escape(text): + return _illegal_chars_in_xml.sub('', _escape(text)) + + +def html_format(text): + return HtmlFormatter().format(_escape(text)) + + +def attribute_escape(attr): + attr = _escape(attr, _attribute_escapes) + return _illegal_chars_in_xml.sub('', attr) + + +def _escape(text, escapes=_generic_escapes): + for name, value in escapes: + if name in text: # performance optimization + text = text.replace(name, value) + return text diff --git a/robot/lib/python3.8/site-packages/robot/utils/markupwriters.py b/robot/lib/python3.8/site-packages/robot/utils/markupwriters.py new file mode 100644 index 0000000000000000000000000000000000000000..e0f57a05921febf8a4c71dbbd9c8233a5bdd08ac --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/markupwriters.py @@ -0,0 +1,108 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .markuputils import attribute_escape, html_escape, xml_escape +from .robottypes import is_string +from .robotio import file_writer + + +class _MarkupWriter(object): + + def __init__(self, output, write_empty=True, usage=None): + """ + :param output: Either an opened, file like object, or a path to the + desired output file. In the latter case, the file is created + and clients should use :py:meth:`close` method to close it. + :param write_empty: Whether to write empty elements and attributes. + """ + if is_string(output): + output = file_writer(output, usage=usage) + self.output = output + self._write_empty = write_empty + self._preamble() + + def _preamble(self): + pass + + def start(self, name, attrs=None, newline=True): + attrs = self._format_attrs(attrs) + self._start(name, attrs, newline) + + def _start(self, name, attrs, newline): + self._write('<%s %s>' % (name, attrs) if attrs else '<%s>' % name, + newline) + + def _format_attrs(self, attrs): + if not attrs: + return '' + attrs = [(k, attribute_escape(attrs[k] or '')) + for k in self._order_attrs(attrs)] + write_empty = self._write_empty + return ' '.join('%s="%s"' % a for a in attrs if write_empty or a[1]) + + def _order_attrs(self, attrs): + return attrs + + def content(self, content=None, escape=True, newline=False): + if content: + self._write(self._escape(content) if escape else content, newline) + + def _escape(self, content): + raise NotImplementedError + + def end(self, name, newline=True): + self._write('' % name, newline) + + def element(self, name, content=None, attrs=None, escape=True, + newline=True, replace_newlines=False): + attrs = self._format_attrs(attrs) + if self._write_empty or content or attrs: + self._start(name, attrs, newline=False) + self.content(content, escape, replace_newlines) + self.end(name, newline) + + def close(self): + """Closes the underlying output file.""" + self.output.close() + + def _write(self, text, newline=False): + self.output.write(text) + if newline: + self.output.write('\n') + + +class HtmlWriter(_MarkupWriter): + + def _order_attrs(self, attrs): + return sorted(attrs) # eases testing + + def _escape(self, content): + return html_escape(content) + + +class XmlWriter(_MarkupWriter): + + def _preamble(self): + self._write('', newline=True) + + def _escape(self, text): + return xml_escape(text) + + +class NullMarkupWriter(object): + """Null implementation of the _MarkupWriter interface.""" + + __init__ = start = content = element = end = close = \ + lambda *args, **kwargs: None diff --git a/robot/lib/python3.8/site-packages/robot/utils/match.py b/robot/lib/python3.8/site-packages/robot/utils/match.py new file mode 100644 index 0000000000000000000000000000000000000000..88ac3440bc903849b84ee10880f9d9327cf1c168 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/match.py @@ -0,0 +1,90 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +import fnmatch +from functools import partial + +from .compat import py2to3 +from .normalizing import normalize +from .platform import IRONPYTHON, PY3 +from .robottypes import is_string + + +def eq(str1, str2, ignore=(), caseless=True, spaceless=True): + str1 = normalize(str1, ignore, caseless, spaceless) + str2 = normalize(str2, ignore, caseless, spaceless) + return str1 == str2 + + +@py2to3 +class Matcher(object): + + def __init__(self, pattern, ignore=(), caseless=True, spaceless=True, + regexp=False): + if PY3 and isinstance(pattern, bytes): + raise TypeError('Matching bytes is not supported on Python 3.') + self.pattern = pattern + self._normalize = partial(normalize, ignore=ignore, caseless=caseless, + spaceless=spaceless) + self._regexp = self._compile(self._normalize(pattern), regexp=regexp) + + def _compile(self, pattern, regexp=False): + if not regexp: + pattern = fnmatch.translate(pattern) + # https://github.com/IronLanguages/ironpython2/issues/515 + if IRONPYTHON and "\\'" in pattern: + pattern = pattern.replace("\\'", "'") + return re.compile(pattern, re.DOTALL) + + def match(self, string): + return self._regexp.match(self._normalize(string)) is not None + + def match_any(self, strings): + return any(self.match(s) for s in strings) + + def __nonzero__(self): + return bool(self._normalize(self.pattern)) + + +class MultiMatcher(object): + + def __init__(self, patterns=None, ignore=(), caseless=True, spaceless=True, + match_if_no_patterns=False, regexp=False): + self._matchers = [Matcher(pattern, ignore, caseless, spaceless, regexp) + for pattern in self._ensure_list(patterns)] + self._match_if_no_patterns = match_if_no_patterns + + def _ensure_list(self, patterns): + if patterns is None: + return [] + if is_string(patterns): + return [patterns] + return patterns + + def match(self, string): + if self._matchers: + return any(m.match(string) for m in self._matchers) + return self._match_if_no_patterns + + def match_any(self, strings): + return any(self.match(s) for s in strings) + + def __len__(self): + return len(self._matchers) + + def __iter__(self): + for matcher in self._matchers: + yield matcher.pattern diff --git a/robot/lib/python3.8/site-packages/robot/utils/misc.py b/robot/lib/python3.8/site-packages/robot/utils/misc.py new file mode 100644 index 0000000000000000000000000000000000000000..93b8bb059a1a7b6917fb13d7cf8404fa6d6a9dbc --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/misc.py @@ -0,0 +1,126 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import division + +from operator import add, sub + +from .platform import PY2 +from .robottypes import is_integer +from .unic import unic + + +def roundup(number, ndigits=0, return_type=None): + """Rounds number to the given number of digits. + + Numbers equally close to a certain precision are always rounded away from + zero. By default return value is float when ``ndigits`` is positive and + int otherwise, but that can be controlled with ``return_type``. + + With the built-in ``round()`` rounding equally close numbers as well as + the return type depends on the Python version. + """ + result = _roundup(number, ndigits) + if not return_type: + return_type = float if ndigits > 0 else int + return return_type(result) + + +# Python 2 rounds half away from zero (as taught in school) but Python 3 +# uses "bankers' rounding" that rounds half towards the even number. We want +# consistent rounding and expect Python 2 style to be more familiar for users. +if PY2: + _roundup = round +else: + def _roundup(number, ndigits): + precision = 10 ** (-1 * ndigits) + if number % (0.5 * precision) == 0 and number % precision != 0: + operator = add if number > 0 else sub + number = operator(number, 0.1 * precision) + return round(number, ndigits) + + +def printable_name(string, code_style=False): + """Generates and returns printable name from the given string. + + Examples: + 'simple' -> 'Simple' + 'name with spaces' -> 'Name With Spaces' + 'more spaces' -> 'More Spaces' + 'Cases AND spaces' -> 'Cases AND Spaces' + '' -> '' + + If 'code_style' is True: + + 'mixedCAPSCamel' -> 'Mixed CAPS Camel' + 'camelCaseName' -> 'Camel Case Name' + 'under_score_name' -> 'Under Score Name' + 'under_and space' -> 'Under And Space' + 'miXed_CAPS_nAMe' -> 'MiXed CAPS NAMe' + '' -> '' + """ + if code_style and '_' in string: + string = string.replace('_', ' ') + parts = string.split() + if code_style and len(parts) == 1 \ + and not (string.isalpha() and string.islower()): + parts = _split_camel_case(parts[0]) + return ' '.join(part[0].upper() + part[1:] for part in parts) + + +def _split_camel_case(string): + tokens = [] + token = [] + for prev, char, next in zip(' ' + string, string, string[1:] + ' '): + if _is_camel_case_boundary(prev, char, next): + if token: + tokens.append(''.join(token)) + token = [char] + else: + token.append(char) + if token: + tokens.append(''.join(token)) + return tokens + + +def _is_camel_case_boundary(prev, char, next): + if prev.isdigit(): + return not char.isdigit() + if char.isupper(): + return next.islower() or prev.isalpha() and not prev.isupper() + return char.isdigit() + + +def plural_or_not(item): + count = item if is_integer(item) else len(item) + return '' if count in (1, -1) else 's' + + +def seq2str(sequence, quote="'", sep=', ', lastsep=' and '): + """Returns sequence in format `'item 1', 'item 2' and 'item 3'`.""" + sequence = [quote + unic(item) + quote for item in sequence] + if not sequence: + return '' + if len(sequence) == 1: + return sequence[0] + last_two = lastsep.join(sequence[-2:]) + return sep.join(sequence[:-2] + [last_two]) + + +def seq2str2(sequence): + """Returns sequence in format `[ item 1 | item 2 | ... ]`.""" + if not sequence: + return '[ ]' + return '[ %s ]' % ' | '.join(unic(item) for item in sequence) diff --git a/robot/lib/python3.8/site-packages/robot/utils/normalizing.py b/robot/lib/python3.8/site-packages/robot/utils/normalizing.py new file mode 100644 index 0000000000000000000000000000000000000000..ead6e5bc937885d666282886cd2f67ae050c2f6e --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/normalizing.py @@ -0,0 +1,130 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +from .platform import IRONPYTHON, JYTHON, PY_VERSION, PY3 +from .robottypes import is_dict_like, is_unicode, MutableMapping + + +def normalize(string, ignore=(), caseless=True, spaceless=True): + """Normalizes given string according to given spec. + + By default string is turned to lower case and all whitespace is removed. + Additional characters can be removed by giving them in ``ignore`` list. + """ + empty = u'' if is_unicode(string) else b'' + if PY3 and isinstance(ignore, bytes): + # Iterating bytes in Python3 yields integers. + ignore = [bytes([i]) for i in ignore] + if spaceless: + # https://bugs.jython.org/issue2772 + if JYTHON and PY_VERSION < (2, 7, 2): + string = normalize_whitespace(string) + string = empty.join(string.split()) + if caseless: + string = lower(string) + ignore = [lower(i) for i in ignore] + # both if statements below enhance performance a little + if ignore: + for ign in ignore: + if ign in string: + string = string.replace(ign, empty) + return string + + +def normalize_whitespace(string): + return re.sub(r'\s', ' ', string, flags=re.UNICODE) + + +# http://ironpython.codeplex.com/workitem/33133 +if IRONPYTHON and PY_VERSION < (2, 7, 5): + def lower(string): + return ('A' + string).lower()[1:] +else: + def lower(string): + return string.lower() + + +class NormalizedDict(MutableMapping): + """Custom dictionary implementation automatically normalizing keys.""" + + def __init__(self, initial=None, ignore=(), caseless=True, spaceless=True): + """Initialized with possible initial value and normalizing spec. + + Initial values can be either a dictionary or an iterable of name/value + pairs. In the latter case items are added in the given order. + + Normalizing spec has exact same semantics as with the :func:`normalize` + function. + """ + self._data = {} + self._keys = {} + self._normalize = lambda s: normalize(s, ignore, caseless, spaceless) + if initial: + self._add_initial(initial) + + def _add_initial(self, initial): + items = initial.items() if hasattr(initial, 'items') else initial + for key, value in items: + self[key] = value + + def __getitem__(self, key): + return self._data[self._normalize(key)] + + def __setitem__(self, key, value): + norm_key = self._normalize(key) + self._data[norm_key] = value + self._keys.setdefault(norm_key, key) + + def __delitem__(self, key): + norm_key = self._normalize(key) + del self._data[norm_key] + del self._keys[norm_key] + + def __iter__(self): + return (self._keys[norm_key] for norm_key in sorted(self._keys)) + + def __len__(self): + return len(self._data) + + def __str__(self): + return '{%s}' % ', '.join('%r: %r' % (key, self[key]) for key in self) + + def __eq__(self, other): + if not is_dict_like(other): + return False + if not isinstance(other, NormalizedDict): + other = NormalizedDict(other) + return self._data == other._data + + def __ne__(self, other): + return not self == other + + def copy(self): + copy = NormalizedDict() + copy._data = self._data.copy() + copy._keys = self._keys.copy() + copy._normalize = self._normalize + return copy + + # Speed-ups. Following methods are faster than default implementations. + + def __contains__(self, key): + return self._normalize(key) in self._data + + def clear(self): + self._data.clear() + self._keys.clear() diff --git a/robot/lib/python3.8/site-packages/robot/utils/platform.py b/robot/lib/python3.8/site-packages/robot/utils/platform.py new file mode 100644 index 0000000000000000000000000000000000000000..711d60598fab31b36cb87d4989b421a145f142f7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/platform.py @@ -0,0 +1,39 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re +import sys + + +java_match = re.match(r'java(\d+)\.(\d+)\.(\d+)', sys.platform) +if java_match: + JYTHON = True + JAVA_VERSION = tuple(int(i) for i in java_match.groups()) +else: + JYTHON = False + JAVA_VERSION = (0, 0, 0) +PY_VERSION = sys.version_info[:3] +PY2 = PY_VERSION[0] == 2 +PY3 = not PY2 +IRONPYTHON = sys.platform == 'cli' +PYPY = 'PyPy' in sys.version +UNIXY = os.sep == '/' +WINDOWS = not UNIXY + +RERAISED_EXCEPTIONS = (KeyboardInterrupt, SystemExit, MemoryError) +if JYTHON: + from java.lang import OutOfMemoryError + RERAISED_EXCEPTIONS += (OutOfMemoryError,) diff --git a/robot/lib/python3.8/site-packages/robot/utils/recommendations.py b/robot/lib/python3.8/site-packages/robot/utils/recommendations.py new file mode 100644 index 0000000000000000000000000000000000000000..6d2bc0e0ad601ac6a5996245794693c2cb0497fc --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/recommendations.py @@ -0,0 +1,83 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import difflib + + +class RecommendationFinder(object): + + def __init__(self, normalizer=None): + self.normalizer = normalizer or (lambda x: x) + self.recommendations = None + + def find_and_format(self, name, candidates, message, max_matches=10): + self.find(name, candidates, max_matches) + return self.format(message) + + def find(self, name, candidates, max_matches=10): + """Return a list of close matches to `name` from `candidates`.""" + if not name or not candidates: + return [] + norm_name = self.normalizer(name) + norm_candidates = self._get_normalized_candidates(candidates) + cutoff = self._calculate_cutoff(norm_name) + norm_matches = difflib.get_close_matches( + norm_name, norm_candidates, n=max_matches, cutoff=cutoff + ) + self.recommendations = self._get_original_candidates( + norm_candidates, norm_matches + ) + return self.recommendations + + def format(self, message, recommendations=None): + """Add recommendations to the given message. + + The recommendation string looks like:: + + Did you mean: + + + + """ + recommendations = recommendations or self.recommendations + if recommendations: + message += " Did you mean:" + for rec in recommendations: + message += "\n %s" % rec + return message + + def _get_normalized_candidates(self, candidates): + norm_candidates = {} + # sort before normalization for consistent Python/Jython ordering + for cand in sorted(candidates): + norm = self.normalizer(cand) + norm_candidates.setdefault(norm, []).append(cand) + return norm_candidates + + def _get_original_candidates(self, norm_candidates, norm_matches): + candidates = [] + for norm_match in norm_matches: + candidates.extend(norm_candidates[norm_match]) + return candidates + + def _calculate_cutoff(self, string, min_cutoff=.5, max_cutoff=.85, + step=.03): + """Calculate a cutoff depending on string length. + + Default values determined by manual tuning until the results + "look right". + """ + cutoff = min_cutoff + len(string) * step + return min(cutoff, max_cutoff) diff --git a/robot/lib/python3.8/site-packages/robot/utils/restreader.py b/robot/lib/python3.8/site-packages/robot/utils/restreader.py new file mode 100644 index 0000000000000000000000000000000000000000..c77a3687d94d71dd1296626caeb483aa80072b24 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/restreader.py @@ -0,0 +1,66 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError + +try: + from docutils.core import publish_doctree + from docutils.parsers.rst.directives import register_directive + from docutils.parsers.rst.directives.body import CodeBlock +except ImportError: + raise DataError("Using reStructuredText test data requires having " + "'docutils' module version 0.9 or newer installed.") + + +class CaptureRobotData(CodeBlock): + + def run(self): + if 'robotframework' in self.arguments: + store = RobotDataStorage(self.state_machine.document) + store.add_data(self.content) + return [] + + +register_directive('code', CaptureRobotData) +register_directive('code-block', CaptureRobotData) +register_directive('sourcecode', CaptureRobotData) + + +class RobotDataStorage(object): + + def __init__(self, doctree): + if not hasattr(doctree, '_robot_data'): + doctree._robot_data = [] + self._robot_data = doctree._robot_data + + def add_data(self, rows): + self._robot_data.extend(rows) + + def get_data(self): + return '\n'.join(self._robot_data) + + def has_data(self): + return bool(self._robot_data) + + +def read_rest_data(rstfile): + doctree = publish_doctree( + rstfile.read(), source_path=rstfile.name, + settings_overrides={ + 'input_encoding': 'UTF-8', + 'report_level': 4 + }) + store = RobotDataStorage(doctree) + return store.get_data() diff --git a/robot/lib/python3.8/site-packages/robot/utils/robotenv.py b/robot/lib/python3.8/site-packages/robot/utils/robotenv.py new file mode 100644 index 0000000000000000000000000000000000000000..3d0981f5b10d79278d28420b9f903579605e918d --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/robotenv.py @@ -0,0 +1,44 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from .encoding import system_decode as decode, system_encode as encode + + +def get_env_var(name, default=None): + try: + value = os.environ[encode(name)] + except KeyError: + return default + else: + return decode(value) + + +def set_env_var(name, value): + os.environ[encode(name)] = encode(value) + + +def del_env_var(name): + value = get_env_var(name) + if value is not None: + del os.environ[encode(name)] + return value + + +def get_env_vars(upper=os.sep != '/'): + # by default, name is upper-cased on Windows regardless interpreter + return dict((name if not upper else name.upper(), get_env_var(name)) + for name in (decode(name) for name in os.environ)) diff --git a/robot/lib/python3.8/site-packages/robot/utils/robotinspect.py b/robot/lib/python3.8/site-packages/robot/utils/robotinspect.py new file mode 100644 index 0000000000000000000000000000000000000000..af5e961d5ab5a0df856323fd07e12ec900b32733 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/robotinspect.py @@ -0,0 +1,36 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .platform import JYTHON + + +if JYTHON: + + from org.python.core import PyReflectedFunction, PyReflectedConstructor + + def is_java_init(init): + return isinstance(init, PyReflectedConstructor) + + def is_java_method(method): + func = method.im_func if hasattr(method, 'im_func') else method + return isinstance(func, PyReflectedFunction) + +else: + + def is_java_init(init): + return False + + def is_java_method(method): + return False diff --git a/robot/lib/python3.8/site-packages/robot/utils/robotio.py b/robot/lib/python3.8/site-packages/robot/utils/robotio.py new file mode 100644 index 0000000000000000000000000000000000000000..48f4d4240b29c0ab6ae4eed26a83430f4ff4af84 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/robotio.py @@ -0,0 +1,85 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import errno +import io +import os.path + +from robot.errors import DataError + +from .error import get_error_message +from .platform import PY3 +from .robottypes import is_pathlike + + +def file_writer(path=None, encoding='UTF-8', newline=None, usage=None): + if path: + if is_pathlike(path): + path = str(path) + create_destination_directory(path, usage) + try: + f = io.open(path, 'w', encoding=encoding, newline=newline) + except EnvironmentError: + usage = '%s file' % usage if usage else 'file' + raise DataError("Opening %s '%s' failed: %s" + % (usage, path, get_error_message())) + else: + f = io.StringIO(newline=newline) + if PY3: + return f + # These streams require written text to be Unicode. We don't want to add + # `u` prefix to all our strings in Python 2, and cannot really use + # `unicode_literals` either because many other Python 2 APIs accept only + # byte strings. + write = f.write + f.write = lambda text: write(unicode(text)) + return f + + +def binary_file_writer(path=None): + if path: + if is_pathlike(path): + path = str(path) + return io.open(path, 'wb') + f = io.BytesIO() + getvalue = f.getvalue + f.getvalue = lambda encoding='UTF-8': getvalue().decode(encoding) + return f + + +def create_destination_directory(path, usage=None): + if is_pathlike(path): + path = str(path) + directory = os.path.dirname(path) + if directory and not os.path.exists(directory): + try: + _makedirs(directory) + except EnvironmentError: + usage = '%s directory' % usage if usage else 'directory' + raise DataError("Creating %s '%s' failed: %s" + % (usage, directory, get_error_message())) + + +def _makedirs(path): + if PY3: + os.makedirs(path, exist_ok=True) + else: + missing = [] + while not os.path.exists(path): + path, name = os.path.split(path) + missing.append(name) + for name in reversed(missing): + path = os.path.join(path, name) + os.mkdir(path) diff --git a/robot/lib/python3.8/site-packages/robot/utils/robotpath.py b/robot/lib/python3.8/site-packages/robot/utils/robotpath.py new file mode 100644 index 0000000000000000000000000000000000000000..38f7a04f9bc61dd10a0c8d67b7d32485bd80f082 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/robotpath.py @@ -0,0 +1,184 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import os.path +import sys + +from robot.errors import DataError + +from .encoding import system_decode +from .platform import IRONPYTHON, JYTHON, PY_VERSION, PY2, WINDOWS +from .robottypes import is_unicode +from .unic import unic + + +if IRONPYTHON and PY_VERSION == (2, 7, 8): + # https://github.com/IronLanguages/ironpython2/issues/371 + def _abspath(path): + if os.path.isabs(path): + if not os.path.splitdrive(path)[0]: + drive = os.path.splitdrive(os.getcwd())[0] + return drive + path + return path + return os.path.abspath(path) +elif WINDOWS and JYTHON and PY_VERSION > (2, 7, 0): + # https://bugs.jython.org/issue2824 + def _abspath(path): + path = os.path.abspath(path) + if path[:1] == '\\' and path[:2] != '\\\\': + drive = os.getcwd()[:2] + path = drive + path + return path +else: + _abspath = os.path.abspath + +if PY2: + from urllib import pathname2url + + def path_to_url(path): + return pathname2url(path.encode('UTF-8')) +else: + from urllib.request import pathname2url as path_to_url + +if WINDOWS: + CASE_INSENSITIVE_FILESYSTEM = True +else: + try: + CASE_INSENSITIVE_FILESYSTEM = os.listdir('/tmp') == os.listdir('/TMP') + except OSError: + CASE_INSENSITIVE_FILESYSTEM = False + + +def normpath(path, case_normalize=False): + """Replacement for os.path.normpath with some enhancements. + + 1. Convert non-Unicode paths to Unicode using the file system encoding. + 2. NFC normalize Unicode paths (affects mainly OSX). + 3. Optionally lower-case paths on case-insensitive file systems. + That includes Windows and also OSX in default configuration. + 4. Turn ``c:`` into ``c:\\`` on Windows instead of keeping it as ``c:``. + """ + if not is_unicode(path): + path = system_decode(path) + path = unic(path) # Handles NFC normalization on OSX + path = os.path.normpath(path) + if case_normalize and CASE_INSENSITIVE_FILESYSTEM: + path = path.lower() + if WINDOWS and len(path) == 2 and path[1] == ':': + return path + '\\' + return path + + +def abspath(path, case_normalize=False): + """Replacement for os.path.abspath with some enhancements and bug fixes. + + 1. Non-Unicode paths are converted to Unicode using file system encoding. + 2. Optionally lower-case paths on case-insensitive file systems. + That includes Windows and also OSX in default configuration. + 3. Turn ``c:`` into ``c:\\`` on Windows instead of ``c:\\current\\path``. + """ + path = normpath(path, case_normalize) + return normpath(_abspath(path), case_normalize) + + +def get_link_path(target, base): + """Returns a relative path to ``target`` from ``base``. + + If ``base`` is an existing file, then its parent directory is considered to + be the base. Otherwise ``base`` is assumed to be a directory. + + The returned path is URL encoded. On Windows returns an absolute path with + ``file:`` prefix if the target is on a different drive. + """ + path = _get_link_path(target, base) + url = path_to_url(path) + if os.path.isabs(path): + url = 'file:' + url + return url + +def _get_link_path(target, base): + target = abspath(target) + base = abspath(base) + if os.path.isfile(base): + base = os.path.dirname(base) + if base == target: + return '.' + base_drive, base_path = os.path.splitdrive(base) + # Target and base on different drives + if os.path.splitdrive(target)[0] != base_drive: + return target + common_len = len(_common_path(base, target)) + if base_path == os.sep: + return target[common_len:] + if common_len == len(base_drive) + len(os.sep): + common_len -= len(os.sep) + dirs_up = os.sep.join([os.pardir] * base[common_len:].count(os.sep)) + path = os.path.join(dirs_up, target[common_len + len(os.sep):]) + return os.path.normpath(path) + +def _common_path(p1, p2): + """Returns the longest path common to p1 and p2. + + Rationale: as os.path.commonprefix is character based, it doesn't consider + path separators as such, so it may return invalid paths: + commonprefix(('/foo/bar/', '/foo/baz.txt')) -> '/foo/ba' (instead of /foo) + """ + while p1 and p2: + if p1 == p2: + return p1 + if len(p1) > len(p2): + p1 = os.path.dirname(p1) + else: + p2 = os.path.dirname(p2) + return '' + + +def find_file(path, basedir='.', file_type=None): + path = os.path.normpath(path.replace('/', os.sep)) + if os.path.isabs(path): + ret = _find_absolute_path(path) + else: + ret = _find_relative_path(path, basedir) + if ret: + return ret + default = file_type or 'File' + file_type = {'Library': 'Test library', + 'Variables': 'Variable file', + 'Resource': 'Resource file'}.get(file_type, default) + raise DataError("%s '%s' does not exist." % (file_type, path)) + + +def _find_absolute_path(path): + if _is_valid_file(path): + return path + return None + + +def _find_relative_path(path, basedir): + for base in [basedir] + sys.path: + if not (base and os.path.isdir(base)): + continue + if not is_unicode(base): + base = system_decode(base) + ret = os.path.abspath(os.path.join(base, path)) + if _is_valid_file(ret): + return ret + return None + + +def _is_valid_file(path): + return os.path.isfile(path) or \ + (os.path.isdir(path) and os.path.isfile(os.path.join(path, '__init__.py'))) diff --git a/robot/lib/python3.8/site-packages/robot/utils/robottime.py b/robot/lib/python3.8/site-packages/robot/utils/robottime.py new file mode 100644 index 0000000000000000000000000000000000000000..06432a4a689f34cc03e6c48e296ce8aae974ffce --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/robottime.py @@ -0,0 +1,416 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import time +import re + +from .normalizing import normalize +from .misc import plural_or_not, roundup +from .robottypes import is_number, is_string + + +_timer_re = re.compile(r'^([+-])?(\d+:)?(\d+):(\d+)(\.\d+)?$') + + +def _get_timetuple(epoch_secs=None): + if epoch_secs is None: # can also be 0 (at least in unit tests) + epoch_secs = time.time() + secs, millis = _float_secs_to_secs_and_millis(epoch_secs) + timetuple = time.localtime(secs)[:6] # from year to secs + return timetuple + (millis,) + +def _float_secs_to_secs_and_millis(secs): + isecs = int(secs) + millis = roundup((secs - isecs) * 1000) + return (isecs, millis) if millis < 1000 else (isecs+1, 0) + + +def timestr_to_secs(timestr, round_to=3): + """Parses time like '1h 10s', '01:00:10' or '42' and returns seconds.""" + if is_string(timestr) or is_number(timestr): + for converter in _number_to_secs, _timer_to_secs, _time_string_to_secs: + secs = converter(timestr) + if secs is not None: + return secs if round_to is None else roundup(secs, round_to) + raise ValueError("Invalid time string '%s'." % timestr) + +def _number_to_secs(number): + try: + return float(number) + except ValueError: + return None + +def _timer_to_secs(number): + match = _timer_re.match(number) + if not match: + return None + prefix, hours, minutes, seconds, millis = match.groups() + seconds = float(minutes) * 60 + float(seconds) + if hours: + seconds += float(hours[:-1]) * 60 * 60 + if millis: + seconds += float(millis[1:]) / 10**len(millis[1:]) + if prefix == '-': + seconds *= -1 + return seconds + +def _time_string_to_secs(timestr): + timestr = _normalize_timestr(timestr) + if not timestr: + return None + millis = secs = mins = hours = days = 0 + if timestr[0] == '-': + sign = -1 + timestr = timestr[1:] + else: + sign = 1 + temp = [] + for c in timestr: + try: + if c == 'x': millis = float(''.join(temp)); temp = [] + elif c == 's': secs = float(''.join(temp)); temp = [] + elif c == 'm': mins = float(''.join(temp)); temp = [] + elif c == 'h': hours = float(''.join(temp)); temp = [] + elif c == 'd': days = float(''.join(temp)); temp = [] + else: temp.append(c) + except ValueError: + return None + if temp: + return None + return sign * (millis/1000 + secs + mins*60 + hours*60*60 + days*60*60*24) + +def _normalize_timestr(timestr): + timestr = normalize(timestr) + for specifier, aliases in [('x', ['millisecond', 'millisec', 'millis', + 'msec', 'ms']), + ('s', ['second', 'sec']), + ('m', ['minute', 'min']), + ('h', ['hour']), + ('d', ['day'])]: + plural_aliases = [a+'s' for a in aliases if not a.endswith('s')] + for alias in plural_aliases + aliases: + if alias in timestr: + timestr = timestr.replace(alias, specifier) + return timestr + + +def secs_to_timestr(secs, compact=False): + """Converts time in seconds to a string representation. + + Returned string is in format like + '1 day 2 hours 3 minutes 4 seconds 5 milliseconds' with following rules: + + - Time parts having zero value are not included (e.g. '3 minutes 4 seconds' + instead of '0 days 0 hours 3 minutes 4 seconds') + - Hour part has a maximun of 23 and minutes and seconds both have 59 + (e.g. '1 minute 40 seconds' instead of '100 seconds') + + If compact has value 'True', short suffixes are used. + (e.g. 1d 2h 3min 4s 5ms) + """ + return _SecsToTimestrHelper(secs, compact).get_value() + + +class _SecsToTimestrHelper: + + def __init__(self, float_secs, compact): + self._compact = compact + self._ret = [] + self._sign, millis, secs, mins, hours, days \ + = self._secs_to_components(float_secs) + self._add_item(days, 'd', 'day') + self._add_item(hours, 'h', 'hour') + self._add_item(mins, 'min', 'minute') + self._add_item(secs, 's', 'second') + self._add_item(millis, 'ms', 'millisecond') + + def get_value(self): + if len(self._ret) > 0: + return self._sign + ' '.join(self._ret) + return '0s' if self._compact else '0 seconds' + + def _add_item(self, value, compact_suffix, long_suffix): + if value == 0: + return + if self._compact: + suffix = compact_suffix + else: + suffix = ' %s%s' % (long_suffix, plural_or_not(value)) + self._ret.append('%d%s' % (value, suffix)) + + def _secs_to_components(self, float_secs): + if float_secs < 0: + sign = '- ' + float_secs = abs(float_secs) + else: + sign = '' + int_secs, millis = _float_secs_to_secs_and_millis(float_secs) + secs = int_secs % 60 + mins = int_secs // 60 % 60 + hours = int_secs // (60 * 60) % 24 + days = int_secs // (60 * 60 * 24) + return sign, millis, secs, mins, hours, days + + +def format_time(timetuple_or_epochsecs, daysep='', daytimesep=' ', timesep=':', + millissep=None): + """Returns a timestamp formatted from given time using separators. + + Time can be given either as a timetuple or seconds after epoch. + + Timetuple is (year, month, day, hour, min, sec[, millis]), where parts must + be integers and millis is required only when millissep is not None. + Notice that this is not 100% compatible with standard Python timetuples + which do not have millis. + + Seconds after epoch can be either an integer or a float. + """ + if is_number(timetuple_or_epochsecs): + timetuple = _get_timetuple(timetuple_or_epochsecs) + else: + timetuple = timetuple_or_epochsecs + daytimeparts = ['%02d' % t for t in timetuple[:6]] + day = daysep.join(daytimeparts[:3]) + time_ = timesep.join(daytimeparts[3:6]) + millis = millissep and '%s%03d' % (millissep, timetuple[6]) or '' + return day + daytimesep + time_ + millis + + +def get_time(format='timestamp', time_=None): + """Return the given or current time in requested format. + + If time is not given, current time is used. How time is returned is + is deternined based on the given 'format' string as follows. Note that all + checks are case insensitive. + + - If 'format' contains word 'epoch' the time is returned in seconds after + the unix epoch. + - If 'format' contains any of the words 'year', 'month', 'day', 'hour', + 'min' or 'sec' only selected parts are returned. The order of the returned + parts is always the one in previous sentence and order of words in + 'format' is not significant. Parts are returned as zero padded strings + (e.g. May -> '05'). + - Otherwise (and by default) the time is returned as a timestamp string in + format '2006-02-24 15:08:31' + """ + time_ = int(time_ or time.time()) + format = format.lower() + # 1) Return time in seconds since epoc + if 'epoch' in format: + return time_ + timetuple = time.localtime(time_) + parts = [] + for i, match in enumerate('year month day hour min sec'.split()): + if match in format: + parts.append('%.2d' % timetuple[i]) + # 2) Return time as timestamp + if not parts: + return format_time(timetuple, daysep='-') + # Return requested parts of the time + elif len(parts) == 1: + return parts[0] + else: + return parts + + +def parse_time(timestr): + """Parses the time string and returns its value as seconds since epoch. + + Time can be given in five different formats: + + 1) Numbers are interpreted as time since epoch directly. It is possible to + use also ints and floats, not only strings containing numbers. + 2) Valid timestamp ('YYYY-MM-DD hh:mm:ss' and 'YYYYMMDD hhmmss'). + 3) 'NOW' (case-insensitive) is the current local time. + 4) 'UTC' (case-insensitive) is the current time in UTC. + 5) Format 'NOW - 1 day' or 'UTC + 1 hour 30 min' is the current local/UTC + time plus/minus the time specified with the time string. + + Seconds are rounded down to avoid getting times in the future. + """ + for method in [_parse_time_epoch, + _parse_time_timestamp, + _parse_time_now_and_utc]: + seconds = method(timestr) + if seconds is not None: + return int(seconds) + raise ValueError("Invalid time format '%s'." % timestr) + +def _parse_time_epoch(timestr): + try: + ret = float(timestr) + except ValueError: + return None + if ret < 0: + raise ValueError("Epoch time must be positive (got %s)." % timestr) + return ret + +def _parse_time_timestamp(timestr): + try: + return timestamp_to_secs(timestr, (' ', ':', '-', '.')) + except ValueError: + return None + +def _parse_time_now_and_utc(timestr): + timestr = timestr.replace(' ', '').lower() + base = _parse_time_now_and_utc_base(timestr[:3]) + if base is not None: + extra = _parse_time_now_and_utc_extra(timestr[3:]) + if extra is not None: + return base + extra + return None + +def _parse_time_now_and_utc_base(base): + now = time.time() + if base == 'now': + return now + if base == 'utc': + zone = time.altzone if time.localtime().tm_isdst else time.timezone + return now + zone + return None + +def _parse_time_now_and_utc_extra(extra): + if not extra: + return 0 + if extra[0] not in ['+', '-']: + return None + return (1 if extra[0] == '+' else -1) * timestr_to_secs(extra[1:]) + + +def get_timestamp(daysep='', daytimesep=' ', timesep=':', millissep='.'): + return TIMESTAMP_CACHE.get_timestamp(daysep, daytimesep, timesep, millissep) + + +def timestamp_to_secs(timestamp, seps=None): + try: + secs = _timestamp_to_millis(timestamp, seps) / 1000.0 + except (ValueError, OverflowError): + raise ValueError("Invalid timestamp '%s'." % timestamp) + else: + return roundup(secs, 3) + + +def secs_to_timestamp(secs, seps=None, millis=False): + if not seps: + seps = ('', ' ', ':', '.' if millis else None) + ttuple = time.localtime(secs)[:6] + if millis: + millis = (secs - int(secs)) * 1000 + ttuple = ttuple + (roundup(millis),) + return format_time(ttuple, *seps) + + +def get_elapsed_time(start_time, end_time): + """Returns the time between given timestamps in milliseconds.""" + if start_time == end_time or not (start_time and end_time): + return 0 + if start_time[:-4] == end_time[:-4]: + return int(end_time[-3:]) - int(start_time[-3:]) + start_millis = _timestamp_to_millis(start_time) + end_millis = _timestamp_to_millis(end_time) + # start/end_millis can be long but we want to return int when possible + return int(end_millis - start_millis) + + +def elapsed_time_to_string(elapsed, include_millis=True): + """Converts elapsed time in milliseconds to format 'hh:mm:ss.mil'. + + If `include_millis` is True, '.mil' part is omitted. + """ + prefix = '' + if elapsed < 0: + prefix = '-' + elapsed = abs(elapsed) + if include_millis: + return prefix + _elapsed_time_to_string(elapsed) + return prefix + _elapsed_time_to_string_without_millis(elapsed) + +def _elapsed_time_to_string(elapsed): + secs, millis = divmod(roundup(elapsed), 1000) + mins, secs = divmod(secs, 60) + hours, mins = divmod(mins, 60) + return '%02d:%02d:%02d.%03d' % (hours, mins, secs, millis) + +def _elapsed_time_to_string_without_millis(elapsed): + secs = roundup(elapsed, ndigits=-3) // 1000 + mins, secs = divmod(secs, 60) + hours, mins = divmod(mins, 60) + return '%02d:%02d:%02d' % (hours, mins, secs) + + +def _timestamp_to_millis(timestamp, seps=None): + if seps: + timestamp = _normalize_timestamp(timestamp, seps) + Y, M, D, h, m, s, millis = _split_timestamp(timestamp) + secs = time.mktime(datetime.datetime(Y, M, D, h, m, s).timetuple()) + return roundup(1000*secs + millis) + +def _normalize_timestamp(ts, seps): + for sep in seps: + if sep in ts: + ts = ts.replace(sep, '') + ts = ts.ljust(17, '0') + return '%s%s%s %s:%s:%s.%s' % (ts[:4], ts[4:6], ts[6:8], ts[8:10], + ts[10:12], ts[12:14], ts[14:17]) + +def _split_timestamp(timestamp): + years = int(timestamp[:4]) + mons = int(timestamp[4:6]) + days = int(timestamp[6:8]) + hours = int(timestamp[9:11]) + mins = int(timestamp[12:14]) + secs = int(timestamp[15:17]) + millis = int(timestamp[18:21]) + return years, mons, days, hours, mins, secs, millis + + +class TimestampCache(object): + + def __init__(self): + self._previous_secs = None + self._previous_separators = None + self._previous_timestamp = None + + def get_timestamp(self, daysep='', daytimesep=' ', timesep=':', millissep='.'): + epoch = self._get_epoch() + secs, millis = _float_secs_to_secs_and_millis(epoch) + if self._use_cache(secs, daysep, daytimesep, timesep): + return self._cached_timestamp(millis, millissep) + timestamp = format_time(epoch, daysep, daytimesep, timesep, millissep) + self._cache_timestamp(secs, timestamp, daysep, daytimesep, timesep, millissep) + return timestamp + + # Seam for mocking + def _get_epoch(self): + return time.time() + + def _use_cache(self, secs, *separators): + return self._previous_timestamp \ + and self._previous_secs == secs \ + and self._previous_separators == separators + + def _cached_timestamp(self, millis, millissep): + if millissep: + return self._previous_timestamp + millissep + format(millis, '03d') + return self._previous_timestamp + + def _cache_timestamp(self, secs, timestamp, daysep, daytimesep, timesep, millissep): + self._previous_secs = secs + self._previous_separators = (daysep, daytimesep, timesep) + self._previous_timestamp = timestamp[:-4] if millissep else timestamp + + +TIMESTAMP_CACHE = TimestampCache() diff --git a/robot/lib/python3.8/site-packages/robot/utils/robottypes.py b/robot/lib/python3.8/site-packages/robot/utils/robottypes.py new file mode 100644 index 0000000000000000000000000000000000000000..89e5950f431c1b1016296b4a4467d5bb0fe9f81a --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/robottypes.py @@ -0,0 +1,59 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .platform import PY2 + + +if PY2: + from .robottypes2 import (is_bytes, is_dict_like, is_integer, is_list_like, + is_number, is_pathlike, is_string, + is_unicode, type_name, Mapping, MutableMapping) + unicode = unicode + +else: + from .robottypes3 import (is_bytes, is_dict_like, is_integer, is_list_like, + is_number, is_pathlike, is_string, + is_unicode, type_name, Mapping, MutableMapping) + unicode = str + + +TRUE_STRINGS = {'TRUE', 'YES', 'ON', '1'} +FALSE_STRINGS = {'FALSE', 'NO', 'OFF', '0', 'NONE', ''} + + +def is_truthy(item): + """Returns `True` or `False` depending is the item considered true or not. + + Validation rules: + + - If the value is a string, it is considered false if it is `'FALSE'`, + `'NO'`, `'OFF'`, `'0'`, `'NONE'` or `''`, case-insensitively. + Considering `'NONE'` false is new in RF 3.0.3 and considering `'OFF'` + and `'0'` false is new in RF 3.1. + - Other strings are considered true. + - Other values are handled by using the standard `bool()` function. + + Designed to be used also by external test libraries that want to handle + Boolean values similarly as Robot Framework itself. See also + :func:`is_falsy`. + """ + if is_string(item): + return item.upper() not in FALSE_STRINGS + return bool(item) + + +def is_falsy(item): + """Opposite of :func:`is_truthy`.""" + return not is_truthy(item) diff --git a/robot/lib/python3.8/site-packages/robot/utils/robottypes2.py b/robot/lib/python3.8/site-packages/robot/utils/robottypes2.py new file mode 100644 index 0000000000000000000000000000000000000000..e8ced961d9f67614514bdabf0cd805d0757d134a --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/robottypes2.py @@ -0,0 +1,79 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import Mapping, MutableMapping, Sequence +from UserDict import UserDict +from UserString import UserString +from types import ClassType, NoneType + +try: + from java.lang import String +except ImportError: + String = () + +from .platform import RERAISED_EXCEPTIONS + + +def is_integer(item): + return isinstance(item, (int, long)) + + +def is_number(item): + return isinstance(item, (int, long, float)) + + +def is_bytes(item): + return isinstance(item, (bytes, bytearray)) + + +def is_string(item): + # Returns False with `b'bytes'` on IronPython on purpose. Results of + # `isinstance(item, basestring)` would depend on IronPython 2.7.x version. + return isinstance(item, (str, unicode)) + + +def is_unicode(item): + return isinstance(item, unicode) + + +def is_pathlike(item): + return False + + +def is_list_like(item): + if isinstance(item, (str, unicode, bytes, bytearray, UserString, String, + file)): + return False + try: + iter(item) + except RERAISED_EXCEPTIONS: + raise + except: + return False + else: + return True + + +def is_dict_like(item): + return isinstance(item, (Mapping, UserDict)) + + +def type_name(item, capitalize=False): + cls = item.__class__ if hasattr(item, '__class__') else type(item) + named_types = {str: 'string', unicode: 'string', bool: 'boolean', + int: 'integer', long: 'integer', NoneType: 'None', + dict: 'dictionary', type: 'class', ClassType: 'class'} + name = named_types.get(cls, cls.__name__) + return name.capitalize() if capitalize and name.islower() else name diff --git a/robot/lib/python3.8/site-packages/robot/utils/robottypes3.py b/robot/lib/python3.8/site-packages/robot/utils/robottypes3.py new file mode 100644 index 0000000000000000000000000000000000000000..45a04c3519ff423002805b482a286e95f1a8a3c6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/robottypes3.py @@ -0,0 +1,78 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections.abc import Mapping, MutableMapping, Sequence +from collections import UserString +from io import IOBase + +from .platform import RERAISED_EXCEPTIONS, PY_VERSION + +if PY_VERSION < (3, 6): + from pathlib import PosixPath, WindowsPath + PathLike = (PosixPath, WindowsPath) +else: + from os import PathLike + + +def is_integer(item): + return isinstance(item, int) + + +def is_number(item): + return isinstance(item, (int, float)) + + +def is_bytes(item): + return isinstance(item, (bytes, bytearray)) + + +def is_string(item): + return isinstance(item, str) + + +def is_unicode(item): + return isinstance(item, str) + + +def is_pathlike(item): + return isinstance(item, PathLike) + + +def is_list_like(item): + if isinstance(item, (str, bytes, bytearray, UserString, IOBase)): + return False + try: + iter(item) + except RERAISED_EXCEPTIONS: + raise + except: + return False + else: + return True + + +def is_dict_like(item): + return isinstance(item, Mapping) + + +def type_name(item, capitalize=False): + if isinstance(item, IOBase): + name = 'file' + else: + cls = item.__class__ if hasattr(item, '__class__') else type(item) + named_types = {str: 'string', bool: 'boolean', int: 'integer', + type(None): 'None', dict: 'dictionary', type: 'class'} + name = named_types.get(cls, cls.__name__) + return name.capitalize() if capitalize and name.islower() else name diff --git a/robot/lib/python3.8/site-packages/robot/utils/setter.py b/robot/lib/python3.8/site-packages/robot/utils/setter.py new file mode 100644 index 0000000000000000000000000000000000000000..6de990c05afa78257d81a7952f25de9172d70d64 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/setter.py @@ -0,0 +1,46 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class setter(object): + + def __init__(self, method): + self.method = method + self.attr_name = '_setter__' + method.__name__ + self.__doc__ = method.__doc__ + + def __get__(self, instance, owner): + if instance is None: + return self + try: + return getattr(instance, self.attr_name) + except AttributeError: + raise AttributeError(self.method.__name__) + + def __set__(self, instance, value): + if instance is None: + return + setattr(instance, self.attr_name, self.method(instance, value)) + + +class SetterAwareType(type): + + def __new__(cls, name, bases, dct): + slots = dct.get('__slots__') + if slots is not None: + for item in dct.values(): + if isinstance(item, setter): + slots.append(item.attr_name) + return type.__new__(cls, name, bases, dct) diff --git a/robot/lib/python3.8/site-packages/robot/utils/sortable.py b/robot/lib/python3.8/site-packages/robot/utils/sortable.py new file mode 100644 index 0000000000000000000000000000000000000000..aa1eb9454cd86ec3c6fae9e4c55bac940e5080b5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/sortable.py @@ -0,0 +1,53 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from operator import eq, lt, le, gt, ge + +from .robottypes import type_name + + +class Sortable(object): + """Base class for sorting based self._sort_key""" + + _sort_key = NotImplemented + + def __test(self, operator, other, require_sortable=True): + if isinstance(other, Sortable): + return operator(self._sort_key, other._sort_key) + if not require_sortable: + return False + raise TypeError("Cannot sort '%s' and '%s'." + % (type_name(self), type_name(other))) + + def __eq__(self, other): + return self.__test(eq, other, require_sortable=False) + + def __ne__(self, other): + return not self == other + + def __lt__(self, other): + return self.__test(lt, other) + + def __le__(self, other): + return self.__test(le, other) + + def __gt__(self, other): + return self.__test(gt, other) + + def __ge__(self, other): + return self.__test(ge, other) + + def __hash__(self): + return hash(self._sort_key) diff --git a/robot/lib/python3.8/site-packages/robot/utils/text.py b/robot/lib/python3.8/site-packages/robot/utils/text.py new file mode 100644 index 0000000000000000000000000000000000000000..436f1640a7d39c91f63fa3312385ec2c34ae62b7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/text.py @@ -0,0 +1,188 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from itertools import takewhile +import inspect +import os.path +import re + +from .charwidth import get_char_width +from .misc import seq2str2 +from .platform import JYTHON, PY_VERSION +from .robottypes import is_string, is_unicode +from .unic import unic + + +MAX_ERROR_LINES = 40 +_MAX_ASSIGN_LENGTH = 200 +_MAX_ERROR_LINE_LENGTH = 78 +_ERROR_CUT_EXPLN = ' [ Message content over the limit has been removed. ]' +_TAGS_RE = re.compile(r'\s*tags:(.*)', re.IGNORECASE) + + +def cut_long_message(msg): + if MAX_ERROR_LINES is None: + return msg + lines = msg.splitlines() + lengths = _count_line_lengths(lines) + if sum(lengths) <= MAX_ERROR_LINES: + return msg + start = _prune_excess_lines(lines, lengths) + end = _prune_excess_lines(lines, lengths, from_end=True) + return '\n'.join(start + [_ERROR_CUT_EXPLN] + end) + +def _prune_excess_lines(lines, lengths, from_end=False): + if from_end: + lines.reverse() + lengths.reverse() + ret = [] + total = 0 + limit = MAX_ERROR_LINES // 2 + for line, length in zip(lines[:limit], lengths[:limit]): + if total + length >= limit: + ret.append(_cut_long_line(line, total, from_end)) + break + total += length + ret.append(line) + if from_end: + ret.reverse() + return ret + +def _cut_long_line(line, used, from_end): + available_lines = MAX_ERROR_LINES // 2 - used + available_chars = available_lines * _MAX_ERROR_LINE_LENGTH - 3 + if len(line) > available_chars: + if not from_end: + line = line[:available_chars] + '...' + else: + line = '...' + line[-available_chars:] + return line + +def _count_line_lengths(lines): + return [ _count_virtual_line_length(line) for line in lines ] + +def _count_virtual_line_length(line): + if not line: + return 1 + lines, remainder = divmod(len(line), _MAX_ERROR_LINE_LENGTH) + return lines if not remainder else lines + 1 + + +def format_assign_message(variable, value, cut_long=True): + formatter = {'$': unic, '@': seq2str2, '&': _dict_to_str}[variable[0]] + value = formatter(value) + if cut_long and len(value) > _MAX_ASSIGN_LENGTH: + value = value[:_MAX_ASSIGN_LENGTH] + '...' + return '%s = %s' % (variable, value) + +def _dict_to_str(d): + if not d: + return '{ }' + return '{ %s }' % ' | '.join('%s=%s' % (unic(k), unic(v)) + for k, v in d.items()) + + +def get_console_length(text): + return sum(get_char_width(char) for char in text) + + +def pad_console_length(text, width): + if width < 5: + width = 5 + diff = get_console_length(text) - width + if diff > 0: + text = _lose_width(text, diff+3) + '...' + return _pad_width(text, width) + +def _pad_width(text, width): + more = width - get_console_length(text) + return text + ' ' * more + +def _lose_width(text, diff): + lost = 0 + while lost < diff: + lost += get_console_length(text[-1]) + text = text[:-1] + return text + + +def split_args_from_name_or_path(name): + if os.path.exists(name): + return os.path.abspath(name), [] + index = _get_arg_separator_index_from_name_or_path(name) + if index == -1: + return name, [] + args = name[index+1:].split(name[index]) + name = name[:index] + if os.path.exists(name): + name = os.path.abspath(name) + return name, args + + +def _get_arg_separator_index_from_name_or_path(name): + colon_index = name.find(':') + # Handle absolute Windows paths + if colon_index == 1 and name[2:3] in ('/', '\\'): + colon_index = name.find(':', colon_index+1) + semicolon_index = name.find(';') + if colon_index == -1: + return semicolon_index + if semicolon_index == -1: + return colon_index + return min(colon_index, semicolon_index) + + +def split_tags_from_doc(doc): + doc = doc.rstrip() + tags = [] + if not doc: + return doc, tags + lines = doc.splitlines() + match = _TAGS_RE.match(lines[-1]) + if match: + doc = '\n'.join(lines[:-1]).rstrip() + tags = [tag.strip() for tag in match.group(1).split(',')] + return doc, tags + + +def getdoc(item): + doc = inspect.getdoc(item) or u'' + if is_unicode(doc): + return doc + try: + return doc.decode('UTF-8') + except UnicodeDecodeError: + return unic(doc) + + +def getshortdoc(doc_or_item, linesep='\n'): + if not doc_or_item: + return u'' + doc = doc_or_item if is_string(doc_or_item) else getdoc(doc_or_item) + lines = takewhile(lambda line: line.strip(), doc.splitlines()) + return linesep.join(lines) + + +# https://bugs.jython.org/issue2772 +if JYTHON and PY_VERSION < (2, 7, 2): + trailing_spaces = re.compile('\s+$', re.UNICODE) + + def rstrip(string): + return trailing_spaces.sub('', string) + +else: + + def rstrip(string): + return string.rstrip() diff --git a/robot/lib/python3.8/site-packages/robot/utils/unic.py b/robot/lib/python3.8/site-packages/robot/utils/unic.py new file mode 100644 index 0000000000000000000000000000000000000000..063b0fec3627b678c069ee51484e93c528d0e18b --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/utils/unic.py @@ -0,0 +1,103 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from pprint import PrettyPrinter +from unicodedata import normalize + +from .platform import PY2, PY3 +from .robottypes import is_bytes, is_unicode, unicode + + +def unic(item): + item = _unic(item) + try: + return normalize('NFC', item) + except ValueError: + # https://github.com/IronLanguages/ironpython2/issues/628 + return item + + +if PY2: + + def _unic(item): + if isinstance(item, unicode): + return item + if isinstance(item, (bytes, bytearray)): + try: + return item.decode('ASCII') + except UnicodeError: + return u''.join(chr(b) if b < 128 else '\\x%x' % b + for b in bytearray(item)) + try: + try: + return unicode(item) + except UnicodeError: + return unic(str(item)) + except: + return _unrepresentable_object(item) + +else: + + def _unic(item): + if isinstance(item, str): + return item + if isinstance(item, (bytes, bytearray)): + try: + return item.decode('ASCII') + except UnicodeError: + return ''.join(chr(b) if b < 128 else '\\x%x' % b + for b in item) + try: + return str(item) + except: + return _unrepresentable_object(item) + + +def prepr(item, width=80): + return unic(PrettyRepr(width=width).pformat(item)) + + +class PrettyRepr(PrettyPrinter): + + def format(self, object, context, maxlevels, level): + try: + if is_unicode(object): + return repr(object).lstrip('u'), True, False + if is_bytes(object): + return 'b' + repr(object).lstrip('b'), True, False + return PrettyPrinter.format(self, object, context, maxlevels, level) + except: + return _unrepresentable_object(object), True, False + + if PY3: + + # Don't split strings: https://stackoverflow.com/questions/31485402 + def _format(self, object, *args, **kwargs): + if isinstance(object, (str, bytes, bytearray)): + width = self._width + self._width = sys.maxsize + try: + super()._format(object, *args, **kwargs) + finally: + self._width = width + else: + super()._format(object, *args, **kwargs) + + +def _unrepresentable_object(item): + from .error import get_error_message + return u"" \ + % (item.__class__.__name__, get_error_message()) diff --git a/robot/lib/python3.8/site-packages/robot/variables/__init__.py b/robot/lib/python3.8/site-packages/robot/variables/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fee3e9c31b19cca65bf618541520ccaf71879eef --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/variables/__init__.py @@ -0,0 +1,68 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Implements storing and resolving variables. + +This package is mainly for internal usage, but utilities for finding +variables can be used externally as well. +""" + +import warnings + +from .assigner import VariableAssignment +from .evaluation import evaluate_expression +from .notfound import variable_not_found +from .scopes import VariableScopes +from .search import (search_variable, contains_variable, + is_variable, is_assign, + is_scalar_variable, is_scalar_assign, + is_dict_variable, is_dict_assign, + is_list_variable, is_list_assign, + VariableIterator) +from .tablesetter import VariableTableValue, DictVariableTableValue +from .variables import Variables + + +# TODO: Change DeprecationWarning to more visible UserWarning in RF 3.3/4.0 +# (whichever comes first) and remove these utils in the next major version. + +def is_var(string, identifiers='$@&'): + """Deprecated since RF 3.2. Use ``is_variable`` instead.""" + warnings.warn(is_var.__doc__, DeprecationWarning) + return is_variable(string, identifiers) + + +def is_scalar_var(string): + """Deprecated since RF 3.2. Use ``is_scalar_variable`` instead.""" + warnings.warn(is_scalar_var.__doc__, DeprecationWarning) + return is_scalar_variable(string) + + +def is_list_var(string): + """Deprecated since RF 3.2. Use ``is_list_variable`` instead.""" + warnings.warn(is_list_var.__doc__, DeprecationWarning) + return is_list_variable(string) + + +def is_dict_var(string): + """Deprecated since RF 3.2. Use ``is_dict_variable`` instead.""" + warnings.warn(is_dict_var.__doc__, DeprecationWarning) + return is_dict_variable(string) + + +def contains_var(string, identifiers='$@&'): + """Deprecated since RF 3.2. Use ``contains_variable`` instead.""" + warnings.warn(contains_var.__doc__, DeprecationWarning) + return contains_variable(string, identifiers) diff --git a/robot/lib/python3.8/site-packages/robot/variables/assigner.py b/robot/lib/python3.8/site-packages/robot/variables/assigner.py new file mode 100644 index 0000000000000000000000000000000000000000..d3759be6ca028622c1e46b9c493ed4afe9522954 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/variables/assigner.py @@ -0,0 +1,254 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +from robot.errors import (DataError, ExecutionStatus, HandlerExecutionFailed, + VariableError) +from robot.utils import (ErrorDetails, format_assign_message, get_error_message, + is_number, is_string, prepr, rstrip, type_name) + + +class VariableAssignment(object): + + def __init__(self, assignment): + validator = AssignmentValidator() + try: + self.assignment = [validator.validate(var) for var in assignment] + self.error = None + except DataError as err: + self.assignment = assignment + self.error = err + + def __iter__(self): + return iter(self.assignment) + + def __len__(self): + return len(self.assignment) + + def validate_assignment(self): + if self.error: + raise self.error + + def assigner(self, context): + self.validate_assignment() + return VariableAssigner(self.assignment, context) + + +class AssignmentValidator(object): + + def __init__(self): + self._seen_list = False + self._seen_dict = False + self._seen_any_var = False + self._seen_assign_mark = False + + def validate(self, variable): + variable = self._validate_assign_mark(variable) + self._validate_state(is_list=variable[0] == '@', + is_dict=variable[0] == '&') + return variable + + def _validate_assign_mark(self, variable): + if self._seen_assign_mark: + raise DataError("Assign mark '=' can be used only with the last " + "variable.") + if variable.endswith('='): + self._seen_assign_mark = True + return rstrip(variable[:-1]) + return variable + + def _validate_state(self, is_list, is_dict): + if is_list and self._seen_list: + raise DataError('Assignment can contain only one list variable.') + if self._seen_dict or is_dict and self._seen_any_var: + raise DataError('Dictionary variable cannot be assigned with ' + 'other variables.') + self._seen_list += is_list + self._seen_dict += is_dict + self._seen_any_var = True + + +class VariableAssigner(object): + _valid_extended_attr = re.compile('^[_a-zA-Z]\w*$') + + def __init__(self, assignment, context): + self._assignment = assignment + self._context = context + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_val is None: + return + failure = self._get_failure(exc_type, exc_val, exc_tb) + if failure.can_continue(self._context.in_teardown): + self.assign(failure.return_value) + + def _get_failure(self, exc_type, exc_val, exc_tb): + if isinstance(exc_val, ExecutionStatus): + return exc_val + exc_info = (exc_type, exc_val, exc_tb) + return HandlerExecutionFailed(ErrorDetails(exc_info)) + + def assign(self, return_value): + context = self._context + context.trace(lambda: 'Return: %s' % prepr(return_value)) + resolver = ReturnValueResolver(self._assignment) + for name, value in resolver.resolve(return_value): + if not self._extended_assign(name, value, context.variables): + value = self._normal_assign(name, value, context.variables) + context.info(format_assign_message(name, value)) + + def _extended_assign(self, name, value, variables): + if name[0] != '$' or '.' not in name or name in variables: + return False + base, attr = self._split_extended_assign(name) + try: + var = variables[base] + except DataError: + return False + if not (self._variable_supports_extended_assign(var) and + self._is_valid_extended_attribute(attr)): + return False + try: + setattr(var, attr, value) + except: + raise VariableError("Setting attribute '%s' to variable '%s' " + "failed: %s" % (attr, base, get_error_message())) + return True + + def _split_extended_assign(self, name): + base, attr = name.rsplit('.', 1) + return base.strip() + '}', attr[:-1].strip() + + def _variable_supports_extended_assign(self, var): + return not (is_string(var) or is_number(var)) + + def _is_valid_extended_attribute(self, attr): + return self._valid_extended_attr.match(attr) is not None + + def _normal_assign(self, name, value, variables): + variables[name] = value + # Always return the actually assigned value. + return value if name[0] == '$' else variables[name] + + +def ReturnValueResolver(assignment): + if not assignment: + return NoReturnValueResolver() + if len(assignment) == 1: + return OneReturnValueResolver(assignment[0]) + if any(a[0] == '@' for a in assignment): + return ScalarsAndListReturnValueResolver(assignment) + return ScalarsOnlyReturnValueResolver(assignment) + + +class NoReturnValueResolver(object): + + def resolve(self, return_value): + return [] + + +class OneReturnValueResolver(object): + + def __init__(self, variable): + self._variable = variable + + def resolve(self, return_value): + if return_value is None: + identifier = self._variable[0] + return_value = {'$': None, '@': [], '&': {}}[identifier] + return [(self._variable, return_value)] + + +class _MultiReturnValueResolver(object): + + def __init__(self, variables): + self._variables = variables + self._min_count = len(variables) + + def resolve(self, return_value): + return_value = self._convert_to_list(return_value) + self._validate(len(return_value)) + return self._resolve(return_value) + + def _convert_to_list(self, return_value): + if return_value is None: + return [None] * self._min_count + if is_string(return_value): + self._raise_expected_list(return_value) + try: + return list(return_value) + except TypeError: + self._raise_expected_list(return_value) + + def _raise_expected_list(self, ret): + self._raise('Expected list-like value, got %s.' % type_name(ret)) + + def _raise(self, error): + raise VariableError('Cannot set variables: %s' % error) + + def _validate(self, return_count): + raise NotImplementedError + + def _resolve(self, return_value): + raise NotImplementedError + + +class ScalarsOnlyReturnValueResolver(_MultiReturnValueResolver): + + def _validate(self, return_count): + if return_count != self._min_count: + self._raise('Expected %d return values, got %d.' + % (self._min_count, return_count)) + + def _resolve(self, return_value): + return list(zip(self._variables, return_value)) + + +class ScalarsAndListReturnValueResolver(_MultiReturnValueResolver): + + def __init__(self, variables): + _MultiReturnValueResolver.__init__(self, variables) + self._min_count -= 1 + + def _validate(self, return_count): + if return_count < self._min_count: + self._raise('Expected %d or more return values, got %d.' + % (self._min_count, return_count)) + + def _resolve(self, return_value): + before_vars, list_var, after_vars \ + = self._split_variables(self._variables) + before_items, list_items, after_items \ + = self._split_return(return_value, before_vars, after_vars) + before = list(zip(before_vars, before_items)) + after = list(zip(after_vars, after_items)) + return before + [(list_var, list_items)] + after + + def _split_variables(self, variables): + list_index = [v[0] for v in variables].index('@') + return (variables[:list_index], + variables[list_index], + variables[list_index+1:]) + + def _split_return(self, return_value, before_vars, after_vars): + list_start = len(before_vars) + list_end = len(return_value) - len(after_vars) + return (return_value[:list_start], + return_value[list_start:list_end], + return_value[list_end:]) diff --git a/robot/lib/python3.8/site-packages/robot/variables/evaluation.py b/robot/lib/python3.8/site-packages/robot/variables/evaluation.py new file mode 100644 index 0000000000000000000000000000000000000000..f3ef184045fbda6fdbcf4f897116c59d7a336c2d --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/variables/evaluation.py @@ -0,0 +1,135 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from tokenize import generate_tokens, untokenize +import token + +from robot.errors import DataError +from robot.utils import (get_error_message, is_string, MutableMapping, PY2, + StringIO, type_name) + +from .notfound import variable_not_found + + +if PY2: + import __builtin__ as builtins +else: + import builtins +PYTHON_BUILTINS = set(builtins.__dict__) + + +def evaluate_expression(expression, variable_store, modules=None, + namespace=None): + try: + if not is_string(expression): + raise TypeError("Expression must be string, got %s." + % type_name(expression)) + if not expression: + raise ValueError("Expression cannot be empty.") + return _evaluate(expression, variable_store, modules, namespace) + except: + raise DataError("Evaluating expression '%s' failed: %s" + % (expression, get_error_message())) + + +def _evaluate(expression, variable_store, modules=None, namespace=None): + if '$' in expression: + expression = _decorate_variables(expression, variable_store) + global_ns = _import_modules(modules) if modules else {} + local_ns = EvaluationNamespace(variable_store, namespace) + return eval(expression, global_ns, local_ns) + + +def _decorate_variables(expression, variable_store): + variable_started = False + variable_found = False + tokens = [] + for toknum, tokval, _, _, _ in generate_tokens(StringIO(expression).readline): + if variable_started: + if toknum == token.NAME: + if tokval not in variable_store: + variable_not_found('$%s' % tokval, + variable_store.as_dict(decoration=False), + deco_braces=False) + tokval = 'RF_VAR_' + tokval + variable_found = True + else: + tokens.append((token.ERRORTOKEN, '$')) + variable_started = False + if toknum == token.ERRORTOKEN and tokval == '$': + variable_started = True + else: + tokens.append((toknum, tokval)) + return untokenize(tokens).strip() if variable_found else expression + + +def _import_modules(module_names): + modules = {} + for name in module_names.replace(' ', '').split(','): + if not name: + continue + modules[name] = __import__(name) + # If we just import module 'root.sub', module 'root' is not found. + while '.' in name: + name, _ = name.rsplit('.', 1) + modules[name] = __import__(name) + return modules + + +# TODO: In Python 3 this could probably be just Mapping, not MutableMapping. +# With Python 2 at least list comprehensions need to mutate the evaluation +# namespace. Using just Mapping would allow removing __set/delitem__. +class EvaluationNamespace(MutableMapping): + + def __init__(self, variable_store, namespace=None): + self.namespace = {} if namespace is None else dict(namespace) + self.variables = variable_store + + def __getitem__(self, key): + if key.startswith('RF_VAR_'): + return self.variables[key[7:]] + if key in self.namespace: + return self.namespace[key] + return self._import_module(key) + + def _import_module(self, name): + if name in PYTHON_BUILTINS: + raise KeyError + try: + return __import__(name) + except ImportError: + raise NameError("name '%s' is not defined nor importable as module" + % name) + + def __setitem__(self, key, value): + if key.startswith('RF_VAR_'): + self.variables[key[7:]] = value + else: + self.namespace[key] = value + + def __delitem__(self, key): + if key.startswith('RF_VAR_'): + del self.variables[key[7:]] + else: + del self.namespace[key] + + def __iter__(self): + for key in self.variables: + yield key + for key in self.namespace: + yield key + + def __len__(self): + return len(self.variables) + len(self.namespace) diff --git a/robot/lib/python3.8/site-packages/robot/variables/filesetter.py b/robot/lib/python3.8/site-packages/robot/variables/filesetter.py new file mode 100644 index 0000000000000000000000000000000000000000..e2580c65ccd5ebc16ec0eec44da6d9ea1fc347ce --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/variables/filesetter.py @@ -0,0 +1,148 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect +import io +try: + import yaml +except ImportError: + yaml = None + +from robot.errors import DataError +from robot.output import LOGGER +from robot.utils import (get_error_message, is_dict_like, is_list_like, + is_string, seq2str2, type_name, DotDict, Importer) + + +class VariableFileSetter(object): + + def __init__(self, store): + self._store = store + + def set(self, path_or_variables, args=None, overwrite=False): + variables = self._import_if_needed(path_or_variables, args) + self._set(variables, overwrite) + return variables + + def _import_if_needed(self, path_or_variables, args=None): + if not is_string(path_or_variables): + return path_or_variables + LOGGER.info("Importing variable file '%s' with args %s" + % (path_or_variables, args)) + if path_or_variables.lower().endswith(('.yaml', '.yml')): + importer = YamlImporter() + else: + importer = PythonImporter() + try: + return importer.import_variables(path_or_variables, args) + except: + args = 'with arguments %s ' % seq2str2(args) if args else '' + raise DataError("Processing variable file '%s' %sfailed: %s" + % (path_or_variables, args, get_error_message())) + + def _set(self, variables, overwrite=False): + for name, value in variables: + self._store.add(name, value, overwrite) + + +class YamlImporter(object): + + def import_variables(self, path, args=None): + if args: + raise DataError('YAML variable files do not accept arguments.') + variables = self._import(path) + return [('${%s}' % name, self._dot_dict(value)) + for name, value in variables] + + def _import(self, path): + with io.open(path, encoding='UTF-8') as stream: + variables = self._load_yaml(stream) + if not is_dict_like(variables): + raise DataError('YAML variable file must be a mapping, got %s.' + % type_name(variables)) + return variables.items() + + def _load_yaml(self, stream): + if not yaml: + raise DataError('Using YAML variable files requires PyYAML module ' + 'to be installed. Typically you can install it ' + 'by running `pip install pyyaml`.') + if yaml.__version__.split('.')[0] == '3': + return yaml.load(stream) + return yaml.full_load(stream) + + + def _dot_dict(self, value): + if is_dict_like(value): + value = DotDict((n, self._dot_dict(v)) for n, v in value.items()) + return value + + +class PythonImporter(object): + + def import_variables(self, path, args=None): + importer = Importer('variable file').import_class_or_module_by_path + var_file = importer(path, instantiate_with_args=()) + return self._get_variables(var_file, args) + + def _get_variables(self, var_file, args): + if self._is_dynamic(var_file): + variables = self._get_dynamic(var_file, args) + else: + variables = self._get_static(var_file) + return list(self._decorate_and_validate(variables)) + + def _is_dynamic(self, var_file): + return (hasattr(var_file, 'get_variables') or + hasattr(var_file, 'getVariables')) + + def _get_dynamic(self, var_file, args): + get_variables = (getattr(var_file, 'get_variables', None) or + getattr(var_file, 'getVariables')) + variables = get_variables(*args) + if is_dict_like(variables): + return variables.items() + raise DataError("Expected '%s' to return dict-like value, got %s." + % (get_variables.__name__, type_name(variables))) + + def _get_static(self, var_file): + names = [attr for attr in dir(var_file) if not attr.startswith('_')] + if hasattr(var_file, '__all__'): + names = [name for name in names if name in var_file.__all__] + variables = [(name, getattr(var_file, name)) for name in names] + if not inspect.ismodule(var_file): + variables = [(n, v) for n, v in variables if not callable(v)] + return variables + + def _decorate_and_validate(self, variables): + for name, value in variables: + name = self._decorate(name) + self._validate(name, value) + yield name, value + + def _decorate(self, name): + if name.startswith('LIST__'): + return '@{%s}' % name[6:] + if name.startswith('DICT__'): + return '&{%s}' % name[6:] + return '${%s}' % name + + def _validate(self, name, value): + if name[0] == '@' and not is_list_like(value): + raise DataError("Invalid variable '%s': Expected list-like value, " + "got %s." % (name, type_name(value))) + if name[0] == '&' and not is_dict_like(value): + raise DataError("Invalid variable '%s': Expected dict-like value, " + "got %s." % (name, type_name(value))) diff --git a/robot/lib/python3.8/site-packages/robot/variables/finders.py b/robot/lib/python3.8/site-packages/robot/variables/finders.py new file mode 100644 index 0000000000000000000000000000000000000000..90c26dd5c988b5b3ce29f647a18fbf9bc0c03ef0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/variables/finders.py @@ -0,0 +1,182 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +try: + from java.lang.System import (getProperties as get_java_properties, + getProperty) + get_java_property = lambda name: getProperty(name) if name else None +except ImportError: + get_java_property = lambda name: None + get_java_properties = lambda: {} + +from robot.errors import DataError, VariableError +from robot.utils import (get_env_var, get_env_vars, get_error_message, + is_dict_like, is_list_like, normalize, DotDict, + NormalizedDict) + +from .evaluation import evaluate_expression +from .notfound import variable_not_found +from .search import search_variable, VariableMatch + + +class VariableFinder(object): + + def __init__(self, variable_store): + self._finders = (StoredFinder(variable_store), + NumberFinder(), + EmptyFinder(), + InlinePythonFinder(variable_store), + EnvironmentFinder(), + ExtendedFinder(self)) + self._store = variable_store + + def find(self, variable): + match = self._get_match(variable) + identifier = match.identifier + name = match.name + for finder in self._finders: + if identifier in finder.identifiers: + try: + value = finder.find(name) + except (KeyError, ValueError): + continue + try: + return self._validate_value(value, identifier, name) + except VariableError: + raise + except: + raise VariableError("Resolving variable '%s' failed: %s" + % (name, get_error_message())) + variable_not_found(name, self._store.data) + + def _get_match(self, variable): + if isinstance(variable, VariableMatch): + return variable + match = search_variable(variable) + if match.start != 0 or match.end != len(variable) or match.items: + raise DataError("Invalid variable name '%s'." % variable) + return match + + def _validate_value(self, value, identifier, name): + if identifier == '@': + if not is_list_like(value): + raise VariableError("Value of variable '%s' is not list or " + "list-like." % name) + return list(value) + if identifier == '&': + if not is_dict_like(value): + raise VariableError("Value of variable '%s' is not dictionary " + "or dictionary-like." % name) + return DotDict(value) + return value + + +class StoredFinder(object): + identifiers = '$@&' + + def __init__(self, store): + self._store = store + + def find(self, name): + return self._store[name[2:-1]] + + +class NumberFinder(object): + identifiers = '$' + + def find(self, name): + number = normalize(name)[2:-1] + try: + return self._get_int(number) + except ValueError: + return float(number) + + def _get_int(self, number): + bases = {'0b': 2, '0o': 8, '0x': 16} + if number.startswith(tuple(bases)): + return int(number[2:], bases[number[:2]]) + return int(number) + + +class EmptyFinder(object): + identifiers = '$@&' + find = NormalizedDict({'${EMPTY}': u'', '@{EMPTY}': (), '&{EMPTY}': {}}, + ignore='_').__getitem__ + + +class InlinePythonFinder(object): + identifiers = '$@&' + + def __init__(self, variables): + self._variables = variables + + def find(self, name): + base = name[2:-1] + if not base or base[0] != '{' or base[-1] != '}': + raise ValueError + try: + return evaluate_expression(base[1:-1].strip(), self._variables) + except DataError as err: + raise VariableError("Resolving variable '%s' failed: %s" + % (name, err)) + + +class ExtendedFinder(object): + identifiers = '$@&' + _match_extended = re.compile(r''' + (.+?) # base name (group 1) + ([^\s\w].+) # extended part (group 2) + ''', re.UNICODE|re.VERBOSE).match + + def __init__(self, finder): + self._find_variable = finder.find + + def find(self, name): + match = self._match_extended(name[2:-1]) + if match is None: + raise ValueError + base_name, extended = match.groups() + try: + variable = self._find_variable('${%s}' % base_name) + except DataError as err: + raise VariableError("Resolving variable '%s' failed: %s" + % (name, err.message)) + try: + return eval('_BASE_VAR_' + extended, {'_BASE_VAR_': variable}) + except: + raise VariableError("Resolving variable '%s' failed: %s" + % (name, get_error_message())) + + +class EnvironmentFinder(object): + identifiers = '%' + + def find(self, name): + var_name, has_default, default_value = name[2:-1].partition('=') + for getter in get_env_var, get_java_property: + value = getter(var_name) + if value is not None: + return value + if has_default: # in case if '' is desired default value + return default_value + variable_not_found(name, self._get_candidates(), + "Environment variable '%s' not found." % name) + + def _get_candidates(self): + candidates = dict(get_java_properties()) + candidates.update(get_env_vars()) + return candidates diff --git a/robot/lib/python3.8/site-packages/robot/variables/notfound.py b/robot/lib/python3.8/site-packages/robot/variables/notfound.py new file mode 100644 index 0000000000000000000000000000000000000000..512f835bc7a3c80beb1c0c5c520f2441a3405ec6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/variables/notfound.py @@ -0,0 +1,44 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from functools import partial + +from robot.errors import VariableError +from robot.utils import (is_dict_like, is_list_like, normalize, + RecommendationFinder) + + +def variable_not_found(name, candidates, message=None, deco_braces=True): + """Raise DataError for missing variable name. + + Return recommendations for similar variable names if any are found. + """ + candidates = _decorate_candidates(name[0], candidates, deco_braces) + normalizer = partial(normalize, ignore='$@&%{}_') + message = RecommendationFinder(normalizer).find_and_format( + name, candidates, + message=message or "Variable '%s' not found." % name + ) + raise VariableError(message) + + +def _decorate_candidates(identifier, candidates, deco_braces=True): + template = '%s{%s}' if deco_braces else '%s%s' + is_included = {'$': lambda value: True, + '@': is_list_like, + '&': is_dict_like, + '%': lambda value: True}[identifier] + return [template % (identifier, name) + for name in candidates if is_included(candidates[name])] diff --git a/robot/lib/python3.8/site-packages/robot/variables/replacer.py b/robot/lib/python3.8/site-packages/robot/variables/replacer.py new file mode 100644 index 0000000000000000000000000000000000000000..2d112fa73355e490c2e8a7cd8548b321d1dcff6b --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/variables/replacer.py @@ -0,0 +1,187 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError, VariableError +from robot.output import librarylogger as logger +from robot.utils import (escape, is_dict_like, is_list_like, type_name, + unescape, unic) + +from .search import VariableMatch, search_variable + + +class VariableReplacer(object): + + def __init__(self, variables): + self._variables = variables + + def replace_list(self, items, replace_until=None, ignore_errors=False): + """Replaces variables from a list of items. + + If an item in a list is a @{list} variable its value is returned. + Possible variables from other items are replaced using 'replace_scalar'. + Result is always a list. + + 'replace_until' can be used to limit replacing arguments to certain + index from the beginning. Used with Run Keyword variants that only + want to resolve some of the arguments in the beginning and pass others + to called keywords unmodified. + """ + items = list(items or []) + if replace_until is not None: + return self._replace_list_until(items, replace_until, ignore_errors) + return list(self._replace_list(items, ignore_errors)) + + def _replace_list_until(self, items, replace_until, ignore_errors): + # @{list} variables can contain more or less arguments than needed. + # Therefore we need to go through items one by one, and escape possible + # extra items we got. + replaced = [] + while len(replaced) < replace_until and items: + replaced.extend(self._replace_list([items.pop(0)], ignore_errors)) + if len(replaced) > replace_until: + replaced[replace_until:] = [escape(item) + for item in replaced[replace_until:]] + return replaced + items + + def _replace_list(self, items, ignore_errors): + for item in items: + for value in self._replace_list_item(item, ignore_errors): + yield value + + def _replace_list_item(self, item, ignore_errors): + match = search_variable(item, ignore_errors=ignore_errors) + if not match: + return [unescape(match.string)] + value = self.replace_scalar(match, ignore_errors) + if match.is_list_variable() and is_list_like(value): + return value + return [value] + + def replace_scalar(self, item, ignore_errors=False): + """Replaces variables from a scalar item. + + If the item is not a string it is returned as is. If it is a variable, + its value is returned. Otherwise possible variables are replaced with + 'replace_string'. Result may be any object. + """ + match = self._search_variable(item, ignore_errors=ignore_errors) + if not match: + return unescape(match.string) + return self._replace_scalar(match, ignore_errors) + + def _search_variable(self, item, ignore_errors): + if isinstance(item, VariableMatch): + return item + return search_variable(item, ignore_errors=ignore_errors) + + def _replace_scalar(self, match, ignore_errors=False): + if not match.is_variable(): + return self.replace_string(match, ignore_errors=ignore_errors) + return self._get_variable_value(match, ignore_errors) + + def replace_string(self, item, custom_unescaper=None, ignore_errors=False): + """Replaces variables from a string. Result is always a string. + + Input can also be an already found VariableMatch. + """ + unescaper = custom_unescaper or unescape + match = self._search_variable(item, ignore_errors=ignore_errors) + if not match: + return unic(unescaper(match.string)) + return self._replace_string(match, unescaper, ignore_errors) + + def _replace_string(self, match, unescaper, ignore_errors): + parts = [] + while match: + parts.extend([ + unescaper(match.before), + unic(self._get_variable_value(match, ignore_errors)) + ]) + match = search_variable(match.after, ignore_errors=ignore_errors) + parts.append(unescaper(match.string)) + return ''.join(parts) + + def _get_variable_value(self, match, ignore_errors): + match.resolve_base(self, ignore_errors) + # TODO: Do we anymore need to reserve `*{var}` syntax for anything? + if match.identifier == '*': + logger.warn(r"Syntax '%s' is reserved for future use. Please " + r"escape it like '\%s'." % (match, match)) + return unic(match) + try: + value = self._variables[match] + if match.items: + value = self._get_variable_item(match, value) + except DataError: + if not ignore_errors: + raise + value = unescape(match.match) + return value + + def _get_variable_item(self, match, value): + name = match.name + if match.identifier in '@&': + var = '%s[%s]' % (name, match.items[0]) + logger.warn("Accessing variable items using '%s' syntax " + "is deprecated. Use '$%s' instead." % (var, var[1:])) + for item in match.items: + if is_dict_like(value): + value = self._get_dict_variable_item(name, value, item) + elif hasattr(value, '__getitem__'): + value = self._get_sequence_variable_item(name, value, item) + else: + raise VariableError( + "Variable '%s' is %s, which is not subscriptable, and " + "thus accessing item '%s' from it is not possible. To use " + "'[%s]' as a literal value, it needs to be escaped like " + "'\\[%s]'." % (name, type_name(value), item, item, item) + ) + name = '%s[%s]' % (name, item) + return value + + def _get_sequence_variable_item(self, name, variable, index): + index = self.replace_string(index) + try: + index = self._parse_sequence_variable_index(index, name[0] == '$') + except ValueError: + raise VariableError("%s '%s' used with invalid index '%s'. " + "To use '[%s]' as a literal value, it needs " + "to be escaped like '\\[%s]'." + % (type_name(variable, capitalize=True), name, + index, index, index)) + try: + return variable[index] + except IndexError: + raise VariableError("%s '%s' has no item in index %d." + % (type_name(variable, capitalize=True), name, + index)) + + def _parse_sequence_variable_index(self, index, support_slice=True): + if ':' not in index: + return int(index) + if index.count(':') > 2 or not support_slice: + raise ValueError + return slice(*[int(i) if i else None for i in index.split(':')]) + + def _get_dict_variable_item(self, name, variable, key): + key = self.replace_scalar(key) + try: + return variable[key] + except KeyError: + raise VariableError("Dictionary '%s' has no key '%s'." + % (name, key)) + except TypeError as err: + raise VariableError("Dictionary '%s' used with invalid key: %s" + % (name, err)) diff --git a/robot/lib/python3.8/site-packages/robot/variables/scopes.py b/robot/lib/python3.8/site-packages/robot/variables/scopes.py new file mode 100644 index 0000000000000000000000000000000000000000..a0290365be40b86398d44ee32fffa3dfe6a4e8a8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/variables/scopes.py @@ -0,0 +1,259 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import tempfile + +from robot.errors import DataError +from robot.output import LOGGER +from robot.utils import abspath, find_file, get_error_details, NormalizedDict + +from .variables import Variables + + +class VariableScopes(object): + + def __init__(self, settings): + self._global = GlobalVariables(settings) + self._suite = None + self._test = None + self._scopes = [self._global] + self._variables_set = SetVariables() + + @property + def current(self): + return self._scopes[-1] + + @property + def _all_scopes(self): + return reversed(self._scopes) + + @property + def _scopes_until_suite(self): + for scope in self._all_scopes: + yield scope + if scope is self._suite: + break + + @property + def _scopes_until_test(self): + for scope in self._scopes_until_suite: + yield scope + if scope is self._test: + break + + def start_suite(self): + self._suite = self._global.copy() + self._scopes.append(self._suite) + self._variables_set.start_suite() + self._variables_set.update(self._suite) + + def end_suite(self): + self._scopes.pop() + self._suite = self._scopes[-1] if len(self._scopes) > 1 else None + self._variables_set.end_suite() + + def start_test(self): + self._test = self._suite.copy() + self._scopes.append(self._test) + self._variables_set.start_test() + + def end_test(self): + self._scopes.pop() + self._test = None + self._variables_set.end_test() + + def start_keyword(self): + kw = self._suite.copy() + self._variables_set.start_keyword() + self._variables_set.update(kw) + self._scopes.append(kw) + + def end_keyword(self): + self._scopes.pop() + self._variables_set.end_keyword() + + def __getitem__(self, name): + return self.current[name] + + def __setitem__(self, name, value): + self.current[name] = value + + def __contains__(self, name): + return name in self.current + + def replace_list(self, items, replace_until=None, ignore_errors=False): + return self.current.replace_list(items, replace_until, ignore_errors) + + def replace_scalar(self, items, ignore_errors=False): + return self.current.replace_scalar(items, ignore_errors) + + def replace_string(self, string, custom_unescaper=None, ignore_errors=False): + return self.current.replace_string(string, custom_unescaper, ignore_errors) + + def set_from_file(self, path, args, overwrite=False): + variables = None + for scope in self._scopes_until_suite: + if variables is None: + variables = scope.set_from_file(path, args, overwrite) + else: + scope.set_from_file(variables, overwrite=overwrite) + + def set_from_variable_table(self, variables, overwrite=False): + for scope in self._scopes_until_suite: + scope.set_from_variable_table(variables, overwrite) + + def resolve_delayed(self): + for scope in self._scopes_until_suite: + scope.resolve_delayed() + + def set_global(self, name, value): + for scope in self._all_scopes: + name, value = self._set_global_suite_or_test(scope, name, value) + self._variables_set.set_global(name, value) + + def _set_global_suite_or_test(self, scope, name, value): + scope[name] = value + # Avoid creating new list/dict objects in different scopes. + if name[0] != '$': + name = '$' + name[1:] + value = scope[name] + return name, value + + def set_suite(self, name, value, top=False, children=False): + if top: + self._scopes[1][name] = value + return + for scope in self._scopes_until_suite: + name, value = self._set_global_suite_or_test(scope, name, value) + if children: + self._variables_set.set_suite(name, value) + + def set_test(self, name, value): + if self._test is None: + raise DataError('Cannot set test variable when no test is started.') + for scope in self._scopes_until_test: + name, value = self._set_global_suite_or_test(scope, name, value) + self._variables_set.set_test(name, value) + + def set_keyword(self, name, value): + self.current[name] = value + self._variables_set.set_keyword(name, value) + + def set_local_variable(self, name, value): + self.current[name] = value + + def as_dict(self, decoration=True): + return self.current.as_dict(decoration=decoration) + + +class GlobalVariables(Variables): + + def __init__(self, settings): + Variables.__init__(self) + self._set_cli_variables(settings) + self._set_built_in_variables(settings) + + def _set_cli_variables(self, settings): + for path, args in settings.variable_files: + try: + path = find_file(path, file_type='Variable file') + self.set_from_file(path, args) + except: + msg, details = get_error_details() + LOGGER.error(msg) + LOGGER.info(details) + for varstr in settings.variables: + try: + name, value = varstr.split(':', 1) + except ValueError: + name, value = varstr, '' + self['${%s}' % name] = value + + def _set_built_in_variables(self, settings): + for name, value in [('${TEMPDIR}', abspath(tempfile.gettempdir())), + ('${EXECDIR}', abspath('.')), + ('${/}', os.sep), + ('${:}', os.pathsep), + ('${\\n}', os.linesep), + ('${SPACE}', ' '), + ('${True}', True), + ('${False}', False), + ('${None}', None), + ('${null}', None), + ('${OUTPUT_DIR}', settings.output_directory), + ('${OUTPUT_FILE}', settings.output or 'NONE'), + ('${REPORT_FILE}', settings.report or 'NONE'), + ('${LOG_FILE}', settings.log or 'NONE'), + ('${DEBUG_FILE}', settings.debug_file or 'NONE'), + ('${LOG_LEVEL}', settings.log_level), + ('${PREV_TEST_NAME}', ''), + ('${PREV_TEST_STATUS}', ''), + ('${PREV_TEST_MESSAGE}', '')]: + self[name] = value + + +class SetVariables(object): + + def __init__(self): + self._suite = None + self._test = None + self._scopes = [] + + def start_suite(self): + if not self._scopes: + self._suite = NormalizedDict(ignore='_') + else: + self._suite = self._scopes[-1].copy() + self._scopes.append(self._suite) + + def end_suite(self): + self._scopes.pop() + self._suite = self._scopes[-1] if self._scopes else None + + def start_test(self): + self._test = self._scopes[-1].copy() + self._scopes.append(self._test) + + def end_test(self): + self._test = None + self._scopes.pop() + + def start_keyword(self): + self._scopes.append(self._scopes[-1].copy()) + + def end_keyword(self): + self._scopes.pop() + + def set_global(self, name, value): + for scope in self._scopes: + if name in scope: + scope.pop(name) + + def set_suite(self, name, value): + self._suite[name] = value + + def set_test(self, name, value): + for scope in reversed(self._scopes): + scope[name] = value + if scope is self._test: + break + + def set_keyword(self, name, value): + self._scopes[-1][name] = value + + def update(self, variables): + for name, value in self._scopes[-1].items(): + variables[name] = value diff --git a/robot/lib/python3.8/site-packages/robot/variables/search.py b/robot/lib/python3.8/site-packages/robot/variables/search.py new file mode 100644 index 0000000000000000000000000000000000000000..5750e13f45eb65ac8fdf525c73c0d97a13365c75 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/variables/search.py @@ -0,0 +1,323 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +from robot.errors import VariableError +from robot.utils import is_string, py2to3, rstrip + + +def search_variable(string, identifiers='$@&%*', ignore_errors=False): + if not (is_string(string) and '{' in string): + return VariableMatch(string) + return VariableSearcher(identifiers, ignore_errors).search(string) + + +def contains_variable(string, identifiers='$@&'): + match = search_variable(string, identifiers, ignore_errors=True) + return bool(match) + + +def is_variable(string, identifiers='$@&'): + match = search_variable(string, identifiers, ignore_errors=True) + return match.is_variable() + + +def is_scalar_variable(string): + return is_variable(string, '$') + + +# See comment to `VariableMatch.is_list/dict_variable` for explanation why +# `is_list/dict_variable` need different implementation than +# `is_scalar_variable` above. This ought to be changed in RF 4.0. + +def is_list_variable(string): + match = search_variable(string, '@', ignore_errors=True) + return match.is_list_variable() + + +def is_dict_variable(string): + match = search_variable(string, '&', ignore_errors=True) + return match.is_dict_variable() + + +def is_assign(string, identifiers='$@&', allow_assign_mark=False): + match = search_variable(string, identifiers, ignore_errors=True) + return match.is_assign(allow_assign_mark) + + +def is_scalar_assign(string, allow_assign_mark=False): + return is_assign(string, '$', allow_assign_mark) + + +def is_list_assign(string, allow_assign_mark=False): + return is_assign(string, '@', allow_assign_mark) + + +def is_dict_assign(string, allow_assign_mark=False): + return is_assign(string, '&', allow_assign_mark) + + +@py2to3 +class VariableMatch(object): + + def __init__(self, string, identifier=None, base=None, items=(), + start=-1, end=-1): + self.string = string + self.identifier = identifier + self.base = base + self.items = items + self.start = start + self.end = end + + def resolve_base(self, variables, ignore_errors=False): + if self.identifier: + internal = search_variable(self.base) + self.base = variables.replace_string( + internal, + custom_unescaper=unescape_variable_syntax, + ignore_errors=ignore_errors, + ) + + @property + def name(self): + return '%s{%s}' % (self.identifier, self.base) if self else None + + @property + def before(self): + return self.string[:self.start] if self.identifier else self.string + + @property + def match(self): + return self.string[self.start:self.end] if self.identifier else None + + @property + def after(self): + return self.string[self.end:] if self.identifier else None + + def is_variable(self): + return bool(self.identifier + and self.base + and self.start == 0 + and self.end == len(self.string)) + + def is_scalar_variable(self): + return self.identifier == '$' and self.is_variable() + + # The reason `is_list/dict_variable` check they don't have items is that + # at the moment e.g. `@{var}[item]` still returns a scalar value. + # This will change in RF 4.0 and then obviously this code must be changed: + # https://github.com/robotframework/robotframework/issues/3487 + + def is_list_variable(self): + return (self.identifier == '@' and self.is_variable() + and not self.items) + + def is_dict_variable(self): + return (self.identifier == '&' and self.is_variable() + and not self.items) + + def is_assign(self, allow_assign_mark=False): + if allow_assign_mark and self.string.endswith('='): + return search_variable(rstrip(self.string[:-1])).is_assign() + return (self.is_variable() + and self.identifier in '$@&' + and not self.items + and not search_variable(self.base)) + + def is_scalar_assign(self, allow_assign_mark=False): + return self.identifier == '$' and self.is_assign(allow_assign_mark) + + def is_list_assign(self, allow_assign_mark=False): + return self.identifier == '@' and self.is_assign(allow_assign_mark) + + def is_dict_assign(self, allow_assign_mark=False): + return self.identifier == '&' and self.is_assign(allow_assign_mark) + + def __nonzero__(self): + return self.identifier is not None + + def __unicode__(self): + if not self: + return '' + items = ''.join('[%s]' % i for i in self.items) if self.items else '' + return '%s{%s}%s' % (self.identifier, self.base, items) + + +class VariableSearcher(object): + + def __init__(self, identifiers, ignore_errors=False): + self.identifiers = identifiers + self._ignore_errors = ignore_errors + self.start = -1 + self.variable_chars = [] + self.item_chars = [] + self.items = [] + self._open_brackets = 0 # Used both with curly and square brackets + self._escaped = False + + def search(self, string): + if not self._search(string): + return VariableMatch(string) + match = VariableMatch(string=string, + identifier=self.variable_chars[0], + base=''.join(self.variable_chars[2:-1]), + start=self.start, + end=self.start + len(self.variable_chars)) + if self.items: + match.items = tuple(self.items) + match.end += sum(len(i) for i in self.items) + 2 * len(self.items) + return match + + def _search(self, string, offset=0): + start = self._find_variable_start(string) + if start == -1: + return False + self.start = start + offset + self._open_brackets += 1 + self.variable_chars = [string[start], '{'] + start += 2 + state = self.variable_state + for char in string[start:]: + state = state(char) + self._escaped = False if char != '\\' else not self._escaped + if state is None: + break + if state: + try: + self._validate_end_state(state) + except VariableError: + if self._ignore_errors: + return False + raise + return True + + def _find_variable_start(self, string): + start = 1 + while True: + start = string.find('{', start) - 1 + if start < 0: + return -1 + if self._start_index_is_ok(string, start): + return start + start += 2 + + def _start_index_is_ok(self, string, index): + return (string[index] in self.identifiers + and not self._is_escaped(string, index)) + + def _is_escaped(self, string, index): + escaped = False + while index > 0 and string[index-1] == '\\': + index -= 1 + escaped = not escaped + return escaped + + def variable_state(self, char): + self.variable_chars.append(char) + if char == '}' and not self._escaped: + self._open_brackets -= 1 + if self._open_brackets == 0: + if not self._can_have_items(): + return None + return self.waiting_item_state + elif char == '{' and not self._escaped: + self._open_brackets += 1 + return self.variable_state + + def _can_have_items(self): + return self.variable_chars[0] in '$@&' + + def waiting_item_state(self, char): + if char == '[': + self._open_brackets += 1 + return self.item_state + return None + + def item_state(self, char): + if char == ']' and not self._escaped: + self._open_brackets -= 1 + if self._open_brackets == 0: + self.items.append(''.join(self.item_chars)) + self.item_chars = [] + # Don't support chained item access with old @ and & syntax. + # The old syntax was deprecated in RF 3.2 and in RF 3.3 it'll + # be reassigned to mean using item in list/dict context. + if self.variable_chars[0] in '@&': + return None + return self.waiting_item_state + elif char == '[' and not self._escaped: + self._open_brackets += 1 + self.item_chars.append(char) + return self.item_state + + def _validate_end_state(self, state): + if state == self.variable_state: + incomplete = ''.join(self.variable_chars) + raise VariableError("Variable '%s' was not closed properly." + % incomplete) + if state == self.item_state: + variable = ''.join(self.variable_chars) + items = ''.join('[%s]' % i for i in self.items) + incomplete = ''.join(self.item_chars) + raise VariableError("Variable item '%s%s[%s' was not closed " + "properly." % (variable, items, incomplete)) + + +def unescape_variable_syntax(item): + + def handle_escapes(match): + escapes, text = match.groups() + if len(escapes) % 2 == 1 and starts_with_variable_or_curly(text): + return escapes[1:] + return escapes + + def starts_with_variable_or_curly(text): + if text[0] in '{}': + return True + match = search_variable(text, ignore_errors=True) + return match and match.start == 0 + + return re.sub(r'(\\+)(?=(.+))', handle_escapes, item) + + +@py2to3 +class VariableIterator(object): + + def __init__(self, string, identifiers='$@&%', ignore_errors=False): + self.string = string + self.identifiers = identifiers + self.ignore_errors = ignore_errors + + def __iter__(self): + remaining = self.string + while True: + match = search_variable(remaining, self.identifiers, + self.ignore_errors) + if not match: + break + remaining = match.after + yield match.before, match.match, remaining + + def __len__(self): + return sum(1 for _ in self) + + def __nonzero__(self): + try: + next(iter(self)) + except StopIteration: + return False + else: + return True diff --git a/robot/lib/python3.8/site-packages/robot/variables/store.py b/robot/lib/python3.8/site-packages/robot/variables/store.py new file mode 100644 index 0000000000000000000000000000000000000000..c3dca5bc7b78ce7293879464895372767198a302 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/variables/store.py @@ -0,0 +1,119 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.errors import DataError, VariableError +from robot.utils import (DotDict, is_dict_like, is_list_like, NormalizedDict, + type_name) + +from .notfound import variable_not_found +from .search import is_assign +from .tablesetter import VariableTableValueBase + + +class VariableStore(object): + + def __init__(self, variables): + self.data = NormalizedDict(ignore='_') + self._variables = variables + + def resolve_delayed(self, item=None): + if item: + return self._resolve_delayed(*item) + for name, value in list(self.data.items()): + try: + self._resolve_delayed(name, value) + except DataError: + pass + + def _resolve_delayed(self, name, value): + if not self._is_resolvable(value): + return value + try: + self.data[name] = value.resolve(self._variables) + except DataError as err: + # Recursive resolving may have already removed variable. + if name in self: + self.remove(name) + value.report_error(err) + variable_not_found('${%s}' % name, self.data, + "Variable '${%s}' not found." % name) + return self.data[name] + + def _is_resolvable(self, value): + try: # isinstance can throw an exception in ironpython and jython + return isinstance(value, VariableTableValueBase) + except Exception: + return False + + def __getitem__(self, name): + return self._resolve_delayed(name, self.data[name]) + + def update(self, store): + self.data.update(store.data) + + def clear(self): + self.data.clear() + + def add(self, name, value, overwrite=True, decorated=True): + if decorated: + name, value = self._undecorate(name, value) + if overwrite or name not in self.data: + self.data[name] = value + + def _undecorate(self, name, value): + if not is_assign(name): + raise DataError("Invalid variable name '%s'." % name) + if name[0] == '@': + if not is_list_like(value): + self._raise_cannot_set_type(name, value, 'list') + value = list(value) + if name[0] == '&': + if not is_dict_like(value): + self._raise_cannot_set_type(name, value, 'dictionary') + value = DotDict(value) + return name[2:-1], value + + def _raise_cannot_set_type(self, name, value, expected): + raise VariableError("Cannot set variable '%s': Expected %s-like value, " + "got %s." % (name, expected, type_name(value))) + + def remove(self, name): + if name in self.data: + self.data.pop(name) + + def __len__(self): + return len(self.data) + + def __iter__(self): + return iter(self.data) + + def __contains__(self, name): + return name in self.data + + def as_dict(self, decoration=True): + if decoration: + variables = (self._decorate(name, self[name]) for name in self) + else: + variables = self.data + return NormalizedDict(variables, ignore='_') + + def _decorate(self, name, value): + if is_dict_like(value): + name = '&{%s}' % name + elif is_list_like(value): + name = '@{%s}' % name + else: + name = '${%s}' % name + return name, value diff --git a/robot/lib/python3.8/site-packages/robot/variables/tablesetter.py b/robot/lib/python3.8/site-packages/robot/variables/tablesetter.py new file mode 100644 index 0000000000000000000000000000000000000000..18b50ba8592d5209029fc0edf569a6d45c1e0ad3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/variables/tablesetter.py @@ -0,0 +1,155 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from contextlib import contextmanager + +from robot.errors import DataError +from robot.utils import DotDict, is_string, split_from_equals, unic + +from .search import is_assign, is_list_variable, is_dict_variable + + +class VariableTableSetter(object): + + def __init__(self, store): + self._store = store + + def set(self, variables, overwrite=False): + for name, value in self._get_items(variables): + self._store.add(name, value, overwrite, decorated=False) + + def _get_items(self, variables): + for var in variables: + if var.error: + var.report_invalid_syntax(var.error) + continue + try: + value = VariableTableValue(var.value, var.name, + var.report_invalid_syntax) + except DataError as err: + var.report_invalid_syntax(err) + else: + yield var.name[2:-1], value + + +def VariableTableValue(value, name, error_reporter=None): + if not is_assign(name): + raise DataError("Invalid variable name '%s'." % name) + VariableTableValue = {'$': ScalarVariableTableValue, + '@': ListVariableTableValue, + '&': DictVariableTableValue}[name[0]] + return VariableTableValue(value, error_reporter) + + +class VariableTableValueBase(object): + + def __init__(self, values, error_reporter=None): + self._values = self._format_values(values) + self._error_reporter = error_reporter + self._resolving = False + + def _format_values(self, values): + return values + + def resolve(self, variables): + with self._avoid_recursion: + return self._replace_variables(self._values, variables) + + @property + @contextmanager + def _avoid_recursion(self): + if self._resolving: + raise DataError('Recursive variable definition.') + self._resolving = True + try: + yield + finally: + self._resolving = False + + def _replace_variables(self, value, variables): + raise NotImplementedError + + def report_error(self, error): + if self._error_reporter: + self._error_reporter(unic(error)) + + +class ScalarVariableTableValue(VariableTableValueBase): + + def _format_values(self, values): + separator = None + if is_string(values): + values = [values] + elif values and values[0].startswith('SEPARATOR='): + separator = values[0][10:] + values = values[1:] + return separator, values + + def _replace_variables(self, values, variables): + separator, values = values + # Avoid converting single value to string. + if self._is_single_value(separator, values): + return variables.replace_scalar(values[0]) + if separator is None: + separator = ' ' + separator = variables.replace_string(separator) + values = variables.replace_list(values) + return separator.join(unic(item) for item in values) + + def _is_single_value(self, separator, values): + return (separator is None and len(values) == 1 and + not is_list_variable(values[0])) + + +class ListVariableTableValue(VariableTableValueBase): + + def _replace_variables(self, values, variables): + return variables.replace_list(values) + + +class DictVariableTableValue(VariableTableValueBase): + + def _format_values(self, values): + return list(self._yield_formatted(values)) + + def _yield_formatted(self, values): + for item in values: + if is_dict_variable(item): + yield item + else: + name, value = split_from_equals(item) + if value is None: + raise DataError( + "Invalid dictionary variable item '%s'. " + "Items must use 'name=value' syntax or be dictionary " + "variables themselves." % item + ) + yield name, value + + def _replace_variables(self, values, variables): + try: + return DotDict(self._yield_replaced(values, + variables.replace_scalar)) + except TypeError as err: + raise DataError('Creating dictionary failed: %s' % err) + + def _yield_replaced(self, values, replace_scalar): + for item in values: + if isinstance(item, tuple): + key, values = item + yield replace_scalar(key), replace_scalar(values) + else: + for key, values in replace_scalar(item).items(): + yield key, values diff --git a/robot/lib/python3.8/site-packages/robot/variables/variables.py b/robot/lib/python3.8/site-packages/robot/variables/variables.py new file mode 100644 index 0000000000000000000000000000000000000000..369c277b1b97f28e43a4244774be3cdc1eb4146d --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/variables/variables.py @@ -0,0 +1,82 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from robot.utils import is_list_like, type_name + +from .filesetter import VariableFileSetter +from .finders import VariableFinder +from .replacer import VariableReplacer +from .store import VariableStore +from .tablesetter import VariableTableSetter + + +class Variables(object): + """Represents a set of variables. + + Contains methods for replacing variables from list, scalars, and strings. + On top of ${scalar}, @{list} and &{dict} variables, these methods handle + also %{environment} variables. + """ + + def __init__(self): + self.store = VariableStore(self) + self._replacer = VariableReplacer(self) + self._finder = VariableFinder(self.store) + + def __setitem__(self, name, value): + self.store.add(name, value) + + def __getitem__(self, item): + return self._finder.find(item) + + def __contains__(self, name): + return name in self.store + + def resolve_delayed(self): + self.store.resolve_delayed() + + def replace_list(self, items, replace_until=None, ignore_errors=False): + if not is_list_like(items): + raise ValueError("'replace_list' requires list-like input, " + "got %s." % type_name(items)) + return self._replacer.replace_list(items, replace_until, ignore_errors) + + def replace_scalar(self, item, ignore_errors=False): + return self._replacer.replace_scalar(item, ignore_errors) + + def replace_string(self, item, custom_unescaper=None, ignore_errors=False): + return self._replacer.replace_string(item, custom_unescaper, ignore_errors) + + def set_from_file(self, path_or_variables, args=None, overwrite=False): + setter = VariableFileSetter(self.store) + return setter.set(path_or_variables, args, overwrite) + + def set_from_variable_table(self, variables, overwrite=False): + setter = VariableTableSetter(self.store) + setter.set(variables, overwrite) + + def clear(self): + self.store.clear() + + def copy(self): + variables = Variables() + variables.store.data = self.store.data.copy() + return variables + + def update(self, variables): + self.store.update(variables.store) + + def as_dict(self, decoration=True): + return self.store.as_dict(decoration=decoration) diff --git a/robot/lib/python3.8/site-packages/robot/version.py b/robot/lib/python3.8/site-packages/robot/version.py new file mode 100644 index 0000000000000000000000000000000000000000..31fd66fcec0d3c56a45ae5f87cf16f6423f30884 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robot/version.py @@ -0,0 +1,46 @@ +# Copyright 2008-2015 Nokia Networks +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +import sys + +# Version number typically updated by running `invoke set-version `. +# Run `invoke --help set-version` or see tasks.py for details. +VERSION = '3.2.2' + + +def get_version(naked=False): + if naked: + return re.split('(a|b|rc|.dev)', VERSION)[0] + return VERSION + + +def get_full_version(program=None, naked=False): + version = '%s %s (%s %s on %s)' % (program or '', + get_version(naked), + get_interpreter(), + sys.version.split()[0], + sys.platform) + return version.strip() + + +def get_interpreter(): + if sys.platform.startswith('java'): + return 'Jython' + if sys.platform == 'cli': + return 'IronPython' + if 'PyPy' in sys.version: + return 'PyPy' + return 'Python' diff --git a/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/AUTHORS.rst b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/AUTHORS.rst new file mode 100644 index 0000000000000000000000000000000000000000..f38d70c772305cef733b0b55460d2f2ce38b5881 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/AUTHORS.rst @@ -0,0 +1,108 @@ +Robot Framework contributors +============================ + +This file lists contributors to Robot Framework during the time it was +developed at Nokia Networks. There are both `core team members`_ as well +as `other contributors`_. + +Nowadays contributors are acknowledged separately in the release notes +and this file is not anymore updated. Contributors can also be found easily +via GitHub__. + +__ https://github.com/robotframework/robotframework/graphs/contributors + +Core team members +----------------- + +The members of the Robot Framework core team during the Nokia Networks years +2005 - 2015. + +=========================== =========== + Name Years +=========================== =========== +Pekka Klärck (né Laukkanen) 2005 - 2015 +Petri Haapio 2005 - 2008 +Juha Rantanen 2005 - 2011 +Lasse Koskela 2005 - 2006 +Janne Härkönen 2006 - 2012 +Sami Honkonen 2006 - 2007 +Antti Tuomaala 2006 +Ran Nyman 2007 - 2010 +Heikki Hulkko 2007 - 2009 +Anna Tevaniemi 2008 +Kari Husa 2009 - 2011 +Jussi Malinen 2010 - 2015 +Mikko Korpela 2010 - 2014 +Ismo Aro 2010 - 2012 +Ilmari Kontulainen 2010 - 2013 +Tommi Asiala 2012 +Mika Hänninen 2012 - 2013 +Tatu Kairi 2012 - 2014 +Anssi Syrjäsalo 2013 +Janne Piironen 2013 - 2014 +=========================== =========== + +Other contributors +------------------ + +External contributors after Robot Framework was open sourced in 2008. +As already mentioned above, this list is not anymore updated. + +=========================== =============================================== + Name Contribution +=========================== =============================================== +Elisabeth Hendrickson | Quick Start Guide (2.0) +Marcin Michalak | String library (2.1) +Chris Prinos | reST (HTML) support (2.1) + | How-to debug execution with `pdb` (2.7.6) +Régis Desgroppes | Fixing installation paths (2.1.3) + | xUnit compatible outputs (2.5.5) +Robert Spielmann | Report background colors (2.5) +Xie Yanbo | Alignment of east asian characters (2.5.3) +JSXGraph Developers | JSXGraph tool and license changes for it (2.6) +Imran | Template names to listener API (2.6) + | Suite source to listener API (2.7) +Tatu Aalto | Get Time keyword enhancement (2.7.5) +Eemeli Kantola | Fix for non-breaking spaces (2.7.5) +Martti Haukijärvi | IronPython support to Screenshot library (2.7.5) +Guy Kisel | How-to use decorators when creating libraries (2.7.7) + | BuiltIn.Log pprint support (2.8.6) + | New pattern matching keywords in Collections (2.8.6) + | Keyword/variable not found recommendations (2.8.6) + | Tidy ELSE and ELSE IF onto separate lines (2.8.7) + | Initial contribution guidelines (2.9, #1805) +Mike Terzo | Better connection errors to Remote library (2.7.7) +Asko Soukka | reST (plain text) support (2.8.2) +Vivek Kumar Verma | reST (plain text) support (2.8.2) +Stefan Zimmermann | `**kwargs` support for dynamic libraries (2.8.2) + | `**kwargs` support for Java libraries (2.8.3) + | `*varargs` support using java.util.List (2.8.3) +Mirel Pehadzic | Terminal emulation for Telnet library (2.8.2) +Diogo Sa-Chaves De Oliveira | Terminal emulation for Telnet library (2.8.2) +Lionel Perrin | Giving custom seed to --randomize (2.8.5) +Michael Walle | Telnet.Write Control Character keyword (2.8.5) + | Telnet.Read Until Prompt strip_prompt option (2.8.7) + | String.Strip String (3.0) +Tero Kinnunen | BDD 'But' prefix ignored (2.8.7) +Heiko Thiery | Enable log level config option for TelnetLibrary (2.8.7) +Nicolae Chedea | Float parameters in FOR IN RANGE (2.8.7) +Jared Hellman | Custom keyword names (2.9) + | Embedded arguments for library keywords (2.9) +Vinicius K. Ruoso | Support multiple listeners per library (2.9) + | Allowing control over connection timeout in Telnet library (2.9.2) + | Suppress docutils errors/warnings with reST format (2.9.2) +Joseph Lorenzini | Exposed ERROR log level for keywords (2.9) +Guillaume Grossetie | Less flashy report and log styles (2.9, #1943) +Ed Brannin | `FOR ... IN ZIP`, `FOR ... IN ENUMERATE` (2.9, #1989) +Moon SungHoon | String.Get Regexp Matches keyword (2.9, #1985) +Hélio Guilherme | Support partial match with String.Get Lines Matching Regexp (2.9, #1836) +Jean-Charles Deville | Make variable errors not exit `Runner keywords` (2.9, #1869) +Laurent Bristiel | Convert examples in User Guide to plain text format (2.9, #1972) +Tim Orling | IronPython support for `Dialogs` library (2.9.2, #1235) +Jozef Behran | Fix `${TEST_MESSAGE}` to reflect current test message (3.0, #2188) +Joong-Hee Lee | Extend 'Repeat Keyword' to support timeout (3.0, #2245) +Anton Nikitin | Should (Not) Contain Any (3.0.1, #2120) +Yang Qian | Support to copy/deepcopy model objects (3.0.1, #2483) +Chris Callan | Case-insensitivity support to various comparison keywords (3.0.1, #2439) +Benjamin Einaudi | Add `--rerunfailedsuites` option (3.0.1, #2117) +=========================== =============================================== diff --git a/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/METADATA b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..3bf607a701048a26bbcd258eddda629c7f1006c4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/METADATA @@ -0,0 +1,191 @@ +Metadata-Version: 2.1 +Name: robotframework +Version: 3.2.2 +Summary: Generic automation framework for acceptance testing and robotic process automation (RPA) +Home-page: http://robotframework.org +Author: Pekka Klärck +Author-email: peke@eliga.fi +License: Apache License 2.0 +Download-URL: https://pypi.python.org/pypi/robotframework +Keywords: robotframework automation testautomation rpa testing acceptancetesting atdd bdd +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: Jython +Classifier: Programming Language :: Python :: Implementation :: IronPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Testing +Classifier: Topic :: Software Development :: Testing :: Acceptance +Classifier: Topic :: Software Development :: Testing :: BDD +Classifier: Framework :: Robot Framework + +Robot Framework +=============== + +.. contents:: + :local: + +Introduction +------------ + +`Robot Framework `_ is a generic open source +automation framework for acceptance testing, acceptance test driven +development (ATDD), and robotic process automation (RPA). It has simple plain +text syntax and it can be extended easily with libraries implemented using +Python or Java. + +Robot Framework is operating system and application independent. The core +framework is implemented using `Python `_, supports both +Python 2 and Python 3, and runs also on `Jython `_ (JVM), +`IronPython `_ (.NET) and `PyPy `_. +The framework has a rich ecosystem around it consisting of various generic +libraries and tools that are developed as separate projects. For more +information about Robot Framework and the ecosystem, see +http://robotframework.org. + +Robot Framework project is hosted on GitHub_ where you can find source code, +an issue tracker, and some further documentation. See `CONTRIBUTING.rst `__ +if you are interested to contribute. Downloads are hosted on PyPI_, except +for the standalone JAR distribution that is on `Maven central`_. + +Robot Framework development is sponsored by `Robot Framework Foundation +`_. + +.. _GitHub: https://github.com/robotframework/robotframework +.. _PyPI: https://pypi.python.org/pypi/robotframework +.. _Maven central: http://search.maven.org/#search%7Cga%7C1%7Ca%3Arobotframework + +.. image:: https://img.shields.io/pypi/v/robotframework.svg?label=version + :target: https://pypi.python.org/pypi/robotframework + :alt: Latest version + +.. image:: https://img.shields.io/pypi/l/robotframework.svg + :target: http://www.apache.org/licenses/LICENSE-2.0.html + :alt: License + +Installation +------------ + +If you already have Python_ with `pip `_ installed, +you can simply run:: + + pip install robotframework + +Alternatively you can get Robot Framework source code by downloading the source +distribution from PyPI_ and extracting it, or by cloning the project repository +from GitHub_. After that you can install the framework with:: + + python setup.py install + +For more detailed installation instructions, including installing Python, +Jython, IronPython and PyPy or installing from git, see `INSTALL.rst `__. + +Example +------- + +Below is a simple example test case for testing login to some system. +You can find more examples with links to related demo projects from +http://robotframework.org. + +.. code:: robotframework + + *** Settings *** + Documentation A test suite with a single test for valid login. + ... + ... This test has a workflow that is created using keywords in + ... the imported resource file. + Resource resource.robot + + *** Test Cases *** + Valid Login + Open Browser To Login Page + Input Username demo + Input Password mode + Submit Credentials + Welcome Page Should Be Open + [Teardown] Close Browser + +Usage +----- + +Tests (or tasks) are executed from the command line using the ``robot`` +command or by executing the ``robot`` module directly like ``python -m robot`` +or ``jython -m robot``. + +The basic usage is giving a path to a test (or task) file or directory as an +argument with possible command line options before the path:: + + robot tests.robot + robot --variable BROWSER:Firefox --outputdir results path/to/tests/ + +Additionally there is the ``rebot`` tool for combining results and otherwise +post-processing outputs:: + + rebot --name Example output1.xml output2.xml + +Run ``robot --help`` and ``rebot --help`` for more information about the command +line usage. For a complete reference manual see `Robot Framework User Guide`_. + +Documentation +------------- + +- `Robot Framework User Guide + `_ +- `Standard libraries + `_ +- `Built-in tools + `_ +- `API documentation + `_ +- `General documentation and demos + `_ + +Support and contact +------------------- + +- `robotframework-users + `_ mailing list +- `Slack `_ community +- `#robotframework `_ + IRC channel on freenode +- `@robotframework `_ on Twitter +- `Other forums `_ + +Contributing +------------ + +Interested to contribute to Robot Framework? Great! In that case it is a good +start by looking at the `Contribution guidelines `_. If you +do not already have an issue you would like to work on, you can check +issues with `good new issue`__ and `help wanted`__ labels. + +Remember also that there are many other tools and libraries in the wider +`Robot Framework ecosystem `_ that you can +contribute to! + +__ https://github.com/robotframework/robotframework/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 +__ https://github.com/robotframework/robotframework/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22 + +License +------- + +Robot Framework is open source software provided under the `Apache License +2.0`__. Robot Framework documentation and other similar content use the +`Creative Commons Attribution 3.0 Unported`__ license. Most libraries and tools +in the ecosystem are also open source, but they may use different licenses. + +__ http://apache.org/licenses/LICENSE-2.0 +__ http://creativecommons.org/licenses/by/3.0 + + diff --git a/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/RECORD b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..b9d52397a5f1d228b7bb4116b3b6d91ff00b7eb9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/RECORD @@ -0,0 +1,494 @@ +../../../bin/rebot,sha256=kezgqKhIGKaZOwYUzMECSkAP8soS71NxnEtoo0VNn7I,264 +../../../bin/robot,sha256=cEKA0G1Oenc3VtBTgp5EYyvUHWCnWZpsIuCXQuxjsBQ,258 +robot/__init__.py,sha256=bH2ZMraIZ_C98IUqY6iWNHR65VoB5nRuYetr8dF6ZcU,2225 +robot/__main__.py,sha256=zzFiRtllipcIVPDxOkOilKZk9EjAGVogQAEwREJMD6c,953 +robot/__pycache__/__init__.cpython-38.pyc,, +robot/__pycache__/__main__.cpython-38.pyc,, +robot/__pycache__/errors.cpython-38.pyc,, +robot/__pycache__/jarrunner.cpython-38.pyc,, +robot/__pycache__/libdoc.cpython-38.pyc,, +robot/__pycache__/pythonpathsetter.cpython-38.pyc,, +robot/__pycache__/rebot.cpython-38.pyc,, +robot/__pycache__/run.cpython-38.pyc,, +robot/__pycache__/testdoc.cpython-38.pyc,, +robot/__pycache__/tidy.cpython-38.pyc,, +robot/__pycache__/version.cpython-38.pyc,, +robot/api/__init__.py,sha256=j-ADy_ldWYxhA_LdkIvDBm2r6VyvquVkqu8mTaHSeoU,3271 +robot/api/__pycache__/__init__.cpython-38.pyc,, +robot/api/__pycache__/deco.cpython-38.pyc,, +robot/api/__pycache__/logger.cpython-38.pyc,, +robot/api/deco.py,sha256=bwdaRCHDgkXTanxg9sBuFpGY3v-ZXDDDbpvsHcs9_Kg,4928 +robot/api/logger.py,sha256=PcD-itzrZQDjaorZDjYBb9U7bDevQ3xAS5Q0orVELjg,4798 +robot/conf/__init__.py,sha256=3HSNlrU7R4iWtMFyj7mJLubVvx4-F5SKaNBavwDuGtg,1183 +robot/conf/__pycache__/__init__.cpython-38.pyc,, +robot/conf/__pycache__/gatherfailed.cpython-38.pyc,, +robot/conf/__pycache__/settings.cpython-38.pyc,, +robot/conf/gatherfailed.py,sha256=OMr6PmHRIps24Hh9j3Lp75atZXu9HUPsRu3CFdgbVj4,2469 +robot/conf/settings.py,sha256=LRrGyjabjG7IazqDFH4CSUmK_orWyrt3bSahofXxrnU,22798 +robot/errors.py,sha256=Tl8au25NJwYH9MUnMkPOyRKsntY0_oZDFWLJhztOTwI,10137 +robot/htmldata/__init__.py,sha256=GXP0PycDV8A3-Dcd40BFawC944LO8ldAbSFvjyIEnMc,981 +robot/htmldata/__pycache__/__init__.cpython-38.pyc,, +robot/htmldata/__pycache__/htmlfilewriter.cpython-38.pyc,, +robot/htmldata/__pycache__/jartemplate.cpython-38.pyc,, +robot/htmldata/__pycache__/jsonwriter.cpython-38.pyc,, +robot/htmldata/__pycache__/normaltemplate.cpython-38.pyc,, +robot/htmldata/__pycache__/template.cpython-38.pyc,, +robot/htmldata/common/doc_formatting.css,sha256=OM7AqkDIrpuiQTdsSOyT7xf-EKuYH_hu_lIq9dvyy2Q,903 +robot/htmldata/common/js_disabled.css,sha256=fyvx4EZhBUVDyenfQn5heN_ME3ZsrUp4lk6xXl4G-Bc,373 +robot/htmldata/common/storage.js,sha256=Cd71F-SQdhGtp01qulWzbICPoMj2x2kxHeDdAAeiwws,872 +robot/htmldata/htmlfilewriter.py,sha256=G8Uh4X6zHKRGkXSTdxOax2JlBRypBMVaGgXdMFsjIig,3473 +robot/htmldata/jartemplate.py,sha256=7n2zoIr03zvs9T9TYk974L89cdpRL_jsSsSb3i_hvZw,1538 +robot/htmldata/jsonwriter.py,sha256=Z9bkKcS2mIeOvQdIWdyIqu8q_FR0MkY8jXUynUzW0pc,4241 +robot/htmldata/lib/jquery.highlight.min.js,sha256=WV8qBiGnXH3BjlB2znvKEGRgDMyXTL2CKb5u8mMkPzM,1524 +robot/htmldata/lib/jquery.min.js,sha256=CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo,88145 +robot/htmldata/lib/jquery.tablesorter.min.js,sha256=9W9UJKi8FcHpyfC9CqcLYdgJSmkrKn-SGSoH5UdAaRM,43881 +robot/htmldata/lib/jquery.tmpl.min.js,sha256=WP43QdzmB9xqKrut3aT0ifm-hW8mwgxnqCATEBiuFT8,6141 +robot/htmldata/lib/jsxcompressor.min.js,sha256=uCaa-9w1SeVv5pOwSSis4hQb999FXgcAWIpZYYXeh4A,13667 +robot/htmldata/libdoc/libdoc.css,sha256=_898k9SwsCqIDH7qv2cHAlpvlgNN31VqAE12cX0HbDI,4510 +robot/htmldata/libdoc/libdoc.html,sha256=gnbJlCPE0iZ-jE3Rn6zqu7sJs4TlmUN4GQpMilh_KUs,18057 +robot/htmldata/libdoc/print.css,sha256=SstvhjkPJth5Gcwcziabw6Mt48Es_XBEOdyt27yu3dk,138 +robot/htmldata/libdoc/pygments.css,sha256=xbfVXtRwQaS-xqVir90ck3Z23gUWDfArL2kNWbtMMfM,3959 +robot/htmldata/normaltemplate.py,sha256=moIaLchbHQbTuFFguZ20xCtYSSGo1vZE6mkd-Mjr_9U,1091 +robot/htmldata/rebot/common.css,sha256=5d5HJIVx1nOa9q-b4sK8z9QH05hCT9YWbq24Adf3aYM,4402 +robot/htmldata/rebot/fileloading.js,sha256=mBEhW_TcH5IlOn6wiMphWQoqTefj46PExLl1GCbojnA,1487 +robot/htmldata/rebot/log.css,sha256=j4544pif2SqstTW03H12APS3EgunLdRMhfeNtVgzqTQ,7304 +robot/htmldata/rebot/log.html,sha256=KZEQZpSJsvyES7bj6g2pxLFTyaSVjGqiN-kdDOo99Fs,15449 +robot/htmldata/rebot/log.js,sha256=yv_KoU6Kg_X3Qi4E5-sJDsqekOQKL3I5itHuwda9Zbs,7023 +robot/htmldata/rebot/model.js,sha256=kDUXQ63I5-pRsKThNVzldA8HI0PBjGJj5SAE0hzUBOU,10467 +robot/htmldata/rebot/print.css,sha256=BUSAuYWmQSZWzY9lLkOzUZ692rto8wVYibWpHJ1HavI,701 +robot/htmldata/rebot/report.css,sha256=8damdJ1LbCBVHJFSHDv63zRezkxodmsTKs8As7d0lPk,3494 +robot/htmldata/rebot/report.html,sha256=47EaeeqzysnAkiqUQRfvTUl4nNm0XA28svLzDHMOCKI,31666 +robot/htmldata/rebot/testdata.js,sha256=31AwvPPtyNoPczZx4xV04saiNXiM0pHIFWYPk1cu7H4,9760 +robot/htmldata/rebot/util.js,sha256=Sfnubw0UhnHcDBRzEGTeGgzwS0ZD7TezcEGVozgldns,7155 +robot/htmldata/rebot/view.js,sha256=vnTSpy1ECUqMIqNhelr8wvx8mdvCQF2NigGKk7qSqyg,6506 +robot/htmldata/template.py,sha256=5CMIPTqmaD6z8d1LcwjREkXkCIRYVHHHrMJV5VlFhaY,756 +robot/htmldata/testdoc/testdoc.css,sha256=uSURmIgkIYqPSRKS7LWAdqsCwg-Cmfp6T6uvoxA4AXY,153 +robot/htmldata/testdoc/testdoc.html,sha256=iF-m3HOhSVUIaPsVPwKFnhQvwZEFdjJjUv_yVkInq2I,9756 +robot/jarrunner.py,sha256=g2knVRg5xMFXYscwhbTQbKKS9NlnAEmDxDGs63p-FSI,2664 +robot/libdoc.py,sha256=CY_UsbOiZC81153DPikx7Ygp34yP5NqSCf4fWtv9d18,9782 +robot/libdocpkg/__init__.py,sha256=-XxDjElA4UaFwOCUYe34ElIut9_7o1uBub4N6qn1fHs,963 +robot/libdocpkg/__pycache__/__init__.cpython-38.pyc,, +robot/libdocpkg/__pycache__/builder.cpython-38.pyc,, +robot/libdocpkg/__pycache__/consoleviewer.cpython-38.pyc,, +robot/libdocpkg/__pycache__/htmlwriter.cpython-38.pyc,, +robot/libdocpkg/__pycache__/java9builder.cpython-38.pyc,, +robot/libdocpkg/__pycache__/javabuilder.cpython-38.pyc,, +robot/libdocpkg/__pycache__/model.cpython-38.pyc,, +robot/libdocpkg/__pycache__/output.cpython-38.pyc,, +robot/libdocpkg/__pycache__/robotbuilder.cpython-38.pyc,, +robot/libdocpkg/__pycache__/specbuilder.cpython-38.pyc,, +robot/libdocpkg/__pycache__/writer.cpython-38.pyc,, +robot/libdocpkg/__pycache__/xmlwriter.cpython-38.pyc,, +robot/libdocpkg/builder.py,sha256=D_1cWH9pxz6VFzytlC61R67NaPBPtqkurCfKT79AjPE,2157 +robot/libdocpkg/consoleviewer.py,sha256=2jt72FAwlJ3CDQH_prWO6VTgM-rzKnbTWEcVFGQcllw,3733 +robot/libdocpkg/htmlwriter.py,sha256=-69QI65XlRyMbdirf8iyIVXKwfoLH4XtO4KWgnQ5yhg,6019 +robot/libdocpkg/java9builder.py,sha256=Q6uBSdl1QOBNFWXC15qSN6c_LhHxrHOxGQxc4N-AXp8,5469 +robot/libdocpkg/javabuilder.py,sha256=Rjiz2FHo1zTdwaHAB6FmG2gDirYm1Vo1h4SmUbXh8DU,4989 +robot/libdocpkg/model.py,sha256=7kPUlGFIHS8KLiGajw6PcdiZcoFZ_T-TA-QGC0-0cZk,3080 +robot/libdocpkg/output.py,sha256=pE3b-FYMF9KUXRuiAR8nav2__m1ip6YNPS6DKV7oYTs,1380 +robot/libdocpkg/robotbuilder.py,sha256=Qd9I74NULjwIk5Q-vEIUKOhNv3v8JQfHwJlAew1E6ys,6194 +robot/libdocpkg/specbuilder.py,sha256=NLVO8p1D7doeW1CK0WNMr1XM9Z6_bRFAb-PGV-ESgRM,3502 +robot/libdocpkg/writer.py,sha256=xc47R7eBkXCypGMYQfKQedkvLmiOuSLFt63OklAIw1o,1146 +robot/libdocpkg/xmlwriter.py,sha256=Xpro0ovdVKJwEAS5dzTjZJ9tCVejmcdiTTg-1nyKZFo,4829 +robot/libraries/BuiltIn.py,sha256=ema56WiURuLDUAf7kXJHyIcDuMlhw45LNcwuiw8uMF8,166033 +robot/libraries/Collections.py,sha256=umo7Iu7OZlUdkxa1eu65EnzzZirUwTHSnfz-nX_cNq8,43864 +robot/libraries/DateTime.py,sha256=839yP6BbBO373kMpi-JuPVQH28Mh6JKMjE-TnRMMWrQ,30143 +robot/libraries/Dialogs.py,sha256=8z2gDHj9U0aXqnZvRdT16JybhEWwGTF-RPvKVuqpc-Y,5234 +robot/libraries/Easter.py,sha256=z0bNcPmhD-IoTiWtkxfFNFzFPSZHepajaiu4boEsDPU,1104 +robot/libraries/OperatingSystem.py,sha256=zQN1QlcVcLvMpMu0581JDCQXI5cf_lucNt3i3q4eobw,64172 +robot/libraries/Process.py,sha256=TaNUUrSPOwmC4LCZLI71UxkWdp41lAbEpolCMGSyKuo,42275 +robot/libraries/Remote.py,sha256=dIWwgh8G9yyD2b50H-lXJcfjfCCghdpRRqivs7oMGa0,10523 +robot/libraries/Reserved.py,sha256=pWXewi3rcrX2_VTl6fRpKepDn6tV9HOkgZOeu6UUVkA,1213 +robot/libraries/Screenshot.py,sha256=lfwU93X9-7-NagVQS8IGVhsAQKEM4n5BYSVz2yCBfw0,16025 +robot/libraries/String.py,sha256=e3Zcg3-Xw5uS_huLZdjoDt8x-KHJwI792gwcoFpsDpo,34483 +robot/libraries/Telnet.py,sha256=ZJ8AJekLXImfRnm8mlYIP6XsU0rF5wqYqRCPrVteHOc,55077 +robot/libraries/XML.py,sha256=4bCVzPP-DZ6nG6HGQjtmWwhYoSXekeu4GuwHHu_8IXM,70752 +robot/libraries/__init__.py,sha256=nz08bbSOaJj2jdzi5Smggvj59J8Qjb13UEsgp43Cug8,1440 +robot/libraries/__pycache__/BuiltIn.cpython-38.pyc,, +robot/libraries/__pycache__/Collections.cpython-38.pyc,, +robot/libraries/__pycache__/DateTime.cpython-38.pyc,, +robot/libraries/__pycache__/Dialogs.cpython-38.pyc,, +robot/libraries/__pycache__/Easter.cpython-38.pyc,, +robot/libraries/__pycache__/OperatingSystem.cpython-38.pyc,, +robot/libraries/__pycache__/Process.cpython-38.pyc,, +robot/libraries/__pycache__/Remote.cpython-38.pyc,, +robot/libraries/__pycache__/Reserved.cpython-38.pyc,, +robot/libraries/__pycache__/Screenshot.cpython-38.pyc,, +robot/libraries/__pycache__/String.cpython-38.pyc,, +robot/libraries/__pycache__/Telnet.cpython-38.pyc,, +robot/libraries/__pycache__/XML.cpython-38.pyc,, +robot/libraries/__pycache__/__init__.cpython-38.pyc,, +robot/libraries/__pycache__/dialogs_ipy.cpython-38.pyc,, +robot/libraries/__pycache__/dialogs_jy.cpython-38.pyc,, +robot/libraries/__pycache__/dialogs_py.cpython-38.pyc,, +robot/libraries/dialogs_ipy.py,sha256=Nz1AfVlwLLD3HT2_1wHJxA5xeTtkfnRV_1_5UARXRU4,7539 +robot/libraries/dialogs_jy.py,sha256=f6Fb2rtMMkw2gbseCewlmXO-IRd9vs2C6VKCoS1x4p0,5196 +robot/libraries/dialogs_py.py,sha256=yiAnZULhckdd_-nwTSULlTfLgyv6aTfA2K11M8H7buE,6270 +robot/model/__init__.py,sha256=JWLcXf_8CwEw-TMD_TZuGyxj0-EpFqOoSjYOr6c1Dsw,1636 +robot/model/__pycache__/__init__.cpython-38.pyc,, +robot/model/__pycache__/configurer.cpython-38.pyc,, +robot/model/__pycache__/criticality.cpython-38.pyc,, +robot/model/__pycache__/filter.cpython-38.pyc,, +robot/model/__pycache__/itemlist.cpython-38.pyc,, +robot/model/__pycache__/keyword.cpython-38.pyc,, +robot/model/__pycache__/message.cpython-38.pyc,, +robot/model/__pycache__/metadata.cpython-38.pyc,, +robot/model/__pycache__/modelobject.cpython-38.pyc,, +robot/model/__pycache__/modifier.cpython-38.pyc,, +robot/model/__pycache__/namepatterns.cpython-38.pyc,, +robot/model/__pycache__/statistics.cpython-38.pyc,, +robot/model/__pycache__/stats.cpython-38.pyc,, +robot/model/__pycache__/suitestatistics.cpython-38.pyc,, +robot/model/__pycache__/tags.cpython-38.pyc,, +robot/model/__pycache__/tagsetter.cpython-38.pyc,, +robot/model/__pycache__/tagstatistics.cpython-38.pyc,, +robot/model/__pycache__/testcase.cpython-38.pyc,, +robot/model/__pycache__/testsuite.cpython-38.pyc,, +robot/model/__pycache__/totalstatistics.cpython-38.pyc,, +robot/model/__pycache__/visitor.cpython-38.pyc,, +robot/model/configurer.py,sha256=cB9xpMqbD9nRMBykfssqXssg0lMBW0bSF-C2XszWM60,3400 +robot/model/criticality.py,sha256=NscVxkRpAZ6QyL4DVRsl6itP83a81-8dN73wotTu9Ac,1538 +robot/model/filter.py,sha256=RXEUUopRNVHD2MEsvpZ8kHCnKiHM40ItPHg9F_cv30o,3746 +robot/model/itemlist.py,sha256=8rwRQTb4vmhbY6bpcavINfNMprldp30TsS4-TX9kClY,5501 +robot/model/keyword.py,sha256=jJNNdq87E0iD8ex71BB2y1m6-xbq-xpD0-KqqLm91eM,6780 +robot/model/message.py,sha256=A87xelkuVG4zrJWEv4eINPFlcRXXAdZSoCt4UO4uL-k,2679 +robot/model/metadata.py,sha256=WZcSX8272zGL-0drwUwTZPvAKnZMS9kjp9NIAvl1FVA,1167 +robot/model/modelobject.py,sha256=74PfrSdzE69KnsLJpa7qOWI4ccER4h0jNRJgA7Y2xvQ,3246 +robot/model/modifier.py,sha256=rJtxvPt6HwKIBsXEF94njQeHhIsPyu3bmz4QqAGogAY,2090 +robot/model/namepatterns.py,sha256=RvpWT37wXlzyfN-rCWG3Rg64lGFKit3CmkYnFBkD-Wg,1558 +robot/model/statistics.py,sha256=LQZr4sW_J8B-aUhE4wz6YEP0nAsjv2plKeKnXYp_IRU,2659 +robot/model/stats.py,sha256=a2ZDZNdpO7JOfbNTPXn2_8KWksL21e5Gd_-0PxhcOEM,6371 +robot/model/suitestatistics.py,sha256=FLKHTCGSE6yW-0qFrBmiz8xr7exDxmNIi1Z5rPOjTKA,2234 +robot/model/tags.py,sha256=eydzs0G_LvcxdtYhzhAiOqdSAM1CzLvxCCfB0fETWEs,5306 +robot/model/tagsetter.py,sha256=yZKSX2fVYs9nI0aL6jdvKLOZM4NupmL1jxbY9p4IDEQ,1140 +robot/model/tagstatistics.py,sha256=wyYr2t525x4_isuQ07GDiuMsH5dhFJEU4RmV6yIWXQw,6432 +robot/model/testcase.py,sha256=U1R7qzV1fIyPpdHS1U1tecCzAFBbyQSW_aZTRUbATcw,3027 +robot/model/testsuite.py,sha256=WHkNvoQvy7FSqvNcjG_ljtPVJMxKkkBrtMJLoi4NOuU,6630 +robot/model/totalstatistics.py,sha256=p4ziSpsVB4TSGqGpBn1VVxXn43UbY4_Q1_vmbzuHgR4,2545 +robot/model/visitor.py,sha256=7V4M9E0uFHgktCne1CfDd0zMEd1f_HVYAYA9L3O-UM8,6529 +robot/output/__init__.py,sha256=y3pO6irJgHi2p5KROUiEEmtR3tEGhWk3-l1djDC1seY,933 +robot/output/__pycache__/__init__.cpython-38.pyc,, +robot/output/__pycache__/debugfile.cpython-38.pyc,, +robot/output/__pycache__/filelogger.cpython-38.pyc,, +robot/output/__pycache__/librarylogger.cpython-38.pyc,, +robot/output/__pycache__/listenerarguments.cpython-38.pyc,, +robot/output/__pycache__/listenermethods.cpython-38.pyc,, +robot/output/__pycache__/listeners.cpython-38.pyc,, +robot/output/__pycache__/logger.cpython-38.pyc,, +robot/output/__pycache__/loggerhelper.cpython-38.pyc,, +robot/output/__pycache__/output.cpython-38.pyc,, +robot/output/__pycache__/pyloggingconf.cpython-38.pyc,, +robot/output/__pycache__/stdoutlogsplitter.cpython-38.pyc,, +robot/output/__pycache__/xmllogger.cpython-38.pyc,, +robot/output/console/__init__.py,sha256=UhDUOppkbjq6JyaACHn9FDOPPHUIe66_zZ6Ug_fYdms,1373 +robot/output/console/__pycache__/__init__.cpython-38.pyc,, +robot/output/console/__pycache__/dotted.cpython-38.pyc,, +robot/output/console/__pycache__/highlighting.cpython-38.pyc,, +robot/output/console/__pycache__/quiet.cpython-38.pyc,, +robot/output/console/__pycache__/verbose.cpython-38.pyc,, +robot/output/console/dotted.py,sha256=VGZW64WsOT57oOCfTT6LZbNQQRmuE-rC1q0gseGGc60,3430 +robot/output/console/highlighting.py,sha256=7jB5BVm3TeJeQdYWRUvHUizgBQolJz0F4cPdXlDHRTE,6802 +robot/output/console/quiet.py,sha256=YPU4TKAlnWl-uiytm5M9SsSWGyn9ftpvDjXfdcXYge4,1021 +robot/output/console/verbose.py,sha256=Wj9i4oyIFDAHbIrMRPxlESQNjDrEHpxop1uuwSRCFvU,6058 +robot/output/debugfile.py,sha256=gB3yjMubjB3gWtd_pC0EUEFLQl1PpDFneGoPGoXwnrM,3707 +robot/output/filelogger.py,sha256=KFBmyuqbRTHe8tdYfTbNp2wuV_wf5hrZGYImZGCpus0,1926 +robot/output/librarylogger.py,sha256=CdPa0G1vmXGoeKdYCAQAboIq_2Bk9ec4_ClIZNjQVjY,2227 +robot/output/listenerarguments.py,sha256=eIpyAzbyrAjPleOSE7d_ZbLf_Czx149HJ6Ud4-B_DfA,5165 +robot/output/listenermethods.py,sha256=BofFTUSKkKR0VZVtZ5q7cZkhboTPgwNU_O0ZURBEBs8,3840 +robot/output/listeners.py,sha256=z_YFCKNBsFgzJz8BUT_GoR97sKO3PZvJ_coR1HEfA34,6541 +robot/output/logger.py,sha256=y1R2fCb3ApWdxwziJVyc4cCJbhVVI9wCcgqJPqA9STE,8375 +robot/output/loggerhelper.py,sha256=4seF4gFz5y31ISQ48FFtbrMCEF0SOJinxY4pY4FnU2I,4377 +robot/output/output.py,sha256=n7ibpiu96hco19de5wrthYv21FDO_89s1zskfiKb7so,2575 +robot/output/pyloggingconf.py,sha256=dOkBnv8vKKyv6z3Wm_RqCvWqSrpbb5QqMQeUk_OjrS0,2480 +robot/output/stdoutlogsplitter.py,sha256=XA47dp5k3SHUsfbGwLyQgdj7zCPvEkGrMkgLNyVhdaY,2123 +robot/output/xmllogger.py,sha256=MxCdfYQQpaZrJ0x1_G4Fy5hwfnbVEj-fGomp7hm0f7A,5647 +robot/parsing/__init__.py,sha256=-13JcvIB4DxvZB6KMIENn_6EGHsdzNrUs3ZfPXVhiC4,15237 +robot/parsing/__pycache__/__init__.cpython-38.pyc,, +robot/parsing/__pycache__/suitestructure.cpython-38.pyc,, +robot/parsing/lexer/__init__.py,sha256=t4MSlFuHdAPFrfWfJnQGYhoN12d45su90B8cMLC87jA,738 +robot/parsing/lexer/__pycache__/__init__.cpython-38.pyc,, +robot/parsing/lexer/__pycache__/blocklexers.cpython-38.pyc,, +robot/parsing/lexer/__pycache__/context.cpython-38.pyc,, +robot/parsing/lexer/__pycache__/lexer.cpython-38.pyc,, +robot/parsing/lexer/__pycache__/sections.cpython-38.pyc,, +robot/parsing/lexer/__pycache__/settings.cpython-38.pyc,, +robot/parsing/lexer/__pycache__/statementlexers.cpython-38.pyc,, +robot/parsing/lexer/__pycache__/tokenizer.cpython-38.pyc,, +robot/parsing/lexer/__pycache__/tokens.cpython-38.pyc,, +robot/parsing/lexer/blocklexers.py,sha256=nRzqIgOiYfdQmiM-F7CYDO220nMlGU9w0CKy0BvRoz8,6610 +robot/parsing/lexer/context.py,sha256=ujTKs_VEG0XAtkhkEpJzet6ylDD5SiwKznsim_Y040Y,2665 +robot/parsing/lexer/lexer.py,sha256=bw8qQT_9UMR2oRpDt4S_SIcm0gQYo2XUXG_2AzmRzRE,8130 +robot/parsing/lexer/sections.py,sha256=SS-GSTad4mHyLn0EFrm9FY-Oq_ODq0LzQMzB8FaxL6s,3461 +robot/parsing/lexer/settings.py,sha256=KQBhiL2LWIC1IpP4JNLXmMRpHiy8lyUY2-1HTVied9A,6518 +robot/parsing/lexer/statementlexers.py,sha256=BLrPRL2N0upYS0rPZYi7lbPr_Xp78OcQiD0fLDRnZmY,5735 +robot/parsing/lexer/tokenizer.py,sha256=YxowYsuriRd190XChXHvA1b2csrCit_guMLjZ--ZqHk,5058 +robot/parsing/lexer/tokens.py,sha256=YYXDApJYmFgUcbnxBIU0GQc5-42XXODSAKCC3K1lXus,6132 +robot/parsing/model/__init__.py,sha256=Y-jYLtZLSti46luLIAaKLkMOzgshD1XtR0-FIELQtbk,888 +robot/parsing/model/__pycache__/__init__.cpython-38.pyc,, +robot/parsing/model/__pycache__/blocks.cpython-38.pyc,, +robot/parsing/model/__pycache__/statements.cpython-38.pyc,, +robot/parsing/model/__pycache__/visitor.cpython-38.pyc,, +robot/parsing/model/blocks.py,sha256=u_uErE8snuJ-KMErq3hO5m6VZUEeujAnMMbAyT1KY_Y,5095 +robot/parsing/model/statements.py,sha256=rcLs1Y0S0pZz1UxrSuOaKuG2n_EwI6fWKU5DdA8Cv80,10476 +robot/parsing/model/visitor.py,sha256=X-r--mVhn2sAjxUuZPBecdcBPH6qqJbo88XiUBewz6k,2275 +robot/parsing/parser/__init__.py,sha256=1muxSdTIMKtq9gLyyDUNw460Fh6aLz0fMXskNH9gFgE,710 +robot/parsing/parser/__pycache__/__init__.cpython-38.pyc,, +robot/parsing/parser/__pycache__/blockparsers.cpython-38.pyc,, +robot/parsing/parser/__pycache__/fileparser.cpython-38.pyc,, +robot/parsing/parser/__pycache__/parser.cpython-38.pyc,, +robot/parsing/parser/blockparsers.py,sha256=3U0IQVV42aOrL0yvU7vLhf7aWwXo8hIebIIxW3bYVcQ,2658 +robot/parsing/parser/fileparser.py,sha256=vp_3OPg9fw999QOVYi6CVKVLxqrXJ1JGFK0Sjt3aQfI,3794 +robot/parsing/parser/parser.py,sha256=IiMqK2K9AybruwW5YcRR1MynXmSO3cbfABscRjTxt7s,3797 +robot/parsing/suitestructure.py,sha256=DKLu4JquMmC89wylNb0GFlX5M7JsnfuC-qsJq-9nQxU,6187 +robot/pythonpathsetter.py,sha256=Y1pJy6z7_VM-YZEgUHgTpMPeJDevUTqkmxtDBYzcGG4,1482 +robot/rebot.py,sha256=PlQqJ0klkOe2a0Xh8eVR2Qv8J8fzLOdn3DYDWFDoNdc,22858 +robot/reporting/__init__.py,sha256=ZEPLbUFTSHzQEyFqF2v1_TyZbGAGBJAJWDgl3jSyOI8,1192 +robot/reporting/__pycache__/__init__.cpython-38.pyc,, +robot/reporting/__pycache__/expandkeywordmatcher.cpython-38.pyc,, +robot/reporting/__pycache__/jsbuildingcontext.cpython-38.pyc,, +robot/reporting/__pycache__/jsexecutionresult.cpython-38.pyc,, +robot/reporting/__pycache__/jsmodelbuilders.cpython-38.pyc,, +robot/reporting/__pycache__/jswriter.cpython-38.pyc,, +robot/reporting/__pycache__/logreportwriters.cpython-38.pyc,, +robot/reporting/__pycache__/outputwriter.cpython-38.pyc,, +robot/reporting/__pycache__/resultwriter.cpython-38.pyc,, +robot/reporting/__pycache__/stringcache.cpython-38.pyc,, +robot/reporting/__pycache__/xunitwriter.cpython-38.pyc,, +robot/reporting/expandkeywordmatcher.py,sha256=NvBum4hdlqtILjYREskZy5KXOp6nmrnHClSX3L0FBC8,1364 +robot/reporting/jsbuildingcontext.py,sha256=kHFIVNdajg1YRjtVzELOJ5NFUlLcCipT88D9w4kPKac,3801 +robot/reporting/jsexecutionresult.py,sha256=nfgUr4pe6JyH0FKrS8zg7z-fhtoHeIc7PJXZCG1GdRU,3813 +robot/reporting/jsmodelbuilders.py,sha256=3gawwbSLh4ng8amJbzkSlBGr0Hjztz97MkR6aPHZ180,7292 +robot/reporting/jswriter.py,sha256=z0133aAsr3OOUEOu4q8zZf-gueFyQDY608jhvIBYN7U,4046 +robot/reporting/logreportwriters.py,sha256=LefHe5HDtvU9pVzxA5-Wqj8hGeSRD49tG10UWU2_gQs,2490 +robot/reporting/outputwriter.py,sha256=HPFp8-p-OmYJPxFWbh64gUjF8t4XjggRgPcohs5jqMU,1202 +robot/reporting/resultwriter.py,sha256=1AP7GhX9dNYGBC-GmvHYnH5O5mhge7vFJglzuZBLkKI,5818 +robot/reporting/stringcache.py,sha256=H0DM_L8lhlCWSrmvScMofqF7I2ELdm9qiPeLZPKYmyY,1634 +robot/reporting/xunitwriter.py,sha256=4wKzMU27U5KXV9o1dsy6WGqp5GhrBNIsQ6QuElQmeas,3595 +robot/result/__init__.py,sha256=vbmJs0k3rofzZXxQPEtSZmCcflZusl8dgMsghq2QCx8,2004 +robot/result/__pycache__/__init__.cpython-38.pyc,, +robot/result/__pycache__/configurer.cpython-38.pyc,, +robot/result/__pycache__/executionerrors.cpython-38.pyc,, +robot/result/__pycache__/executionresult.cpython-38.pyc,, +robot/result/__pycache__/flattenkeywordmatcher.cpython-38.pyc,, +robot/result/__pycache__/keywordremover.cpython-38.pyc,, +robot/result/__pycache__/merger.cpython-38.pyc,, +robot/result/__pycache__/messagefilter.cpython-38.pyc,, +robot/result/__pycache__/model.cpython-38.pyc,, +robot/result/__pycache__/resultbuilder.cpython-38.pyc,, +robot/result/__pycache__/suiteteardownfailed.cpython-38.pyc,, +robot/result/__pycache__/visitor.cpython-38.pyc,, +robot/result/__pycache__/xmlelementhandlers.cpython-38.pyc,, +robot/result/configurer.py,sha256=6aey28KRYZKzD8s18FNkSuW5cUI-YFlfrVXvvVxsGgs,2853 +robot/result/executionerrors.py,sha256=K3pbVnGsRIIEzTMX788UWgdV1EH1V5TY8Hbr7LKNWCc,1577 +robot/result/executionresult.py,sha256=PNDLlQgHl5FUI_W_810fXO21vSp3dIUYzKWfBaCSG2w,5801 +robot/result/flattenkeywordmatcher.py,sha256=nSiJwrqvYxV-0J7EmSSvwTtsJfPQ6dYk2NhoNXFgO1Q,2414 +robot/result/keywordremover.py,sha256=cb2MQh3oE1O3rSMfHRdVXxJaV3rTedQwUzMzzoJPOl0,5257 +robot/result/merger.py,sha256=n3UPePg9fZZTN8YZ2mHGpAtu8WSnC239FCOO4U48xe0,4751 +robot/result/messagefilter.py,sha256=UiMt5f35Nkvla5enPgbRCaqepN4Jg3ql2ia_FbGY3wc,1015 +robot/result/model.py,sha256=SMXV07g0nmDIDBXdoOscljyeOtU4SZFNKtT3n6s918o,11447 +robot/result/resultbuilder.py,sha256=-TsT1nmdRdB1g2XucwUOde2Y2juFTK6zk6bnLQnXiKM,8100 +robot/result/suiteteardownfailed.py,sha256=Yji3Mdv30twFpr2TQAAcCQjKEYM9EiHTVQBpfaBMYhQ,1529 +robot/result/visitor.py,sha256=B49nnDcuusg6eSioZ0kZMdpaVg_TDt3Hz5dYkGuNF0o,3960 +robot/result/xmlelementhandlers.py,sha256=9U1TczLJjVe560dFXbLNDZr_m6JLHEwgUpXBSH0EWx0,6688 +robot/run.py,sha256=mCbNVyn8Yp4tBPwkReBYZm7UuZQ8IG0ve4YKlmsW2CA,31450 +robot/running/__init__.py,sha256=miNxrC-deAp8SALrwrr0ZwRTW2uRWt8y-qSr5fyMU5A,4207 +robot/running/__pycache__/__init__.cpython-38.pyc,, +robot/running/__pycache__/context.cpython-38.pyc,, +robot/running/__pycache__/dynamicmethods.cpython-38.pyc,, +robot/running/__pycache__/handlers.cpython-38.pyc,, +robot/running/__pycache__/handlerstore.cpython-38.pyc,, +robot/running/__pycache__/importer.cpython-38.pyc,, +robot/running/__pycache__/librarykeywordrunner.cpython-38.pyc,, +robot/running/__pycache__/libraryscopes.cpython-38.pyc,, +robot/running/__pycache__/model.cpython-38.pyc,, +robot/running/__pycache__/namespace.cpython-38.pyc,, +robot/running/__pycache__/outputcapture.cpython-38.pyc,, +robot/running/__pycache__/randomizer.cpython-38.pyc,, +robot/running/__pycache__/runkwregister.cpython-38.pyc,, +robot/running/__pycache__/runner.cpython-38.pyc,, +robot/running/__pycache__/signalhandler.cpython-38.pyc,, +robot/running/__pycache__/status.cpython-38.pyc,, +robot/running/__pycache__/statusreporter.cpython-38.pyc,, +robot/running/__pycache__/steprunner.cpython-38.pyc,, +robot/running/__pycache__/testlibraries.cpython-38.pyc,, +robot/running/__pycache__/usererrorhandler.cpython-38.pyc,, +robot/running/__pycache__/userkeyword.cpython-38.pyc,, +robot/running/__pycache__/userkeywordrunner.cpython-38.pyc,, +robot/running/arguments/__init__.py,sha256=8-RxZqefXbDopLROvalwQe3i1dR8373p3ugYQuJCly0,1051 +robot/running/arguments/__pycache__/__init__.cpython-38.pyc,, +robot/running/arguments/__pycache__/argumentconverter.cpython-38.pyc,, +robot/running/arguments/__pycache__/argumentmapper.cpython-38.pyc,, +robot/running/arguments/__pycache__/argumentparser.cpython-38.pyc,, +robot/running/arguments/__pycache__/argumentresolver.cpython-38.pyc,, +robot/running/arguments/__pycache__/argumentspec.cpython-38.pyc,, +robot/running/arguments/__pycache__/argumentvalidator.cpython-38.pyc,, +robot/running/arguments/__pycache__/embedded.cpython-38.pyc,, +robot/running/arguments/__pycache__/javaargumentcoercer.cpython-38.pyc,, +robot/running/arguments/__pycache__/typeconverters.cpython-38.pyc,, +robot/running/arguments/__pycache__/typevalidator.cpython-38.pyc,, +robot/running/arguments/argumentconverter.py,sha256=aymzFCKEKWLZbipmK0SmeAjjbjjxgG2P06sqOuDipZk,2564 +robot/running/arguments/argumentmapper.py,sha256=gAPy6Y0MmT7PLtq2LFO-N0vPFkUBU6-1gvjyRoWnM7w,3002 +robot/running/arguments/argumentparser.py,sha256=gH-ODpiqa4ABbLOuMkjHxjwaJj5H-4AzsOZ3QJ1EorQ,10820 +robot/running/arguments/argumentresolver.py,sha256=cA6TKRbflVz6NpeKPYAha598BYJM3qWkOBGasKap5E4,4945 +robot/running/arguments/argumentspec.py,sha256=yEWLRNYi6zXKI1THInEmeW9svV0tYaj4cVv85a1NBUE,2662 +robot/running/arguments/argumentvalidator.py,sha256=II6_mZ6MDvBTU88U1grhYndt_lO6CJekSmZ36lJ8YSw,4027 +robot/running/arguments/embedded.py,sha256=4oEDoe8AKMn0TC5HB1sswsQKxT9b9x9oNCh28M8y6GQ,3449 +robot/running/arguments/javaargumentcoercer.py,sha256=lC6vRh48j-_EKZugPqzZTg3JVSJOkqcsKSuG43DyCUc,4520 +robot/running/arguments/typeconverters.py,sha256=ZKoOdVIB4Gb984AIReIJ1_RWaGvgd0MKixNlSUyol7w,10658 +robot/running/arguments/typevalidator.py,sha256=k4Z8QkhbE_un3Bpc1WTTMyxPShO5rO3sWbAPjox4JSU,2339 +robot/running/builder/__init__.py,sha256=JM5r4N4_YVA96qoU41ZJyWZ-RKIswt19AGUXYixI-lk,737 +robot/running/builder/__pycache__/__init__.cpython-38.pyc,, +robot/running/builder/__pycache__/builders.cpython-38.pyc,, +robot/running/builder/__pycache__/parsers.cpython-38.pyc,, +robot/running/builder/__pycache__/testsettings.cpython-38.pyc,, +robot/running/builder/__pycache__/transformers.cpython-38.pyc,, +robot/running/builder/builders.py,sha256=X5b0145XPZg1xII0nbpvtlUmIkRih3Po2it5pVMfnwA,8617 +robot/running/builder/parsers.py,sha256=zFq1LSUzYTqyrmQ19EtZPTirkWB94KxX7BvHOtnmn0w,4853 +robot/running/builder/testsettings.py,sha256=9v9eJN_5ZYsIkkKLEbjelP4I3IYMAc-oA1hRz59wn9M,3429 +robot/running/builder/transformers.py,sha256=qNjL4AkZL7Q1dU7Q6j94gNVdOML_VOVNEUZkyuuiyyE,8983 +robot/running/context.py,sha256=wfwkiLg75ABDOtth-92LswJnXHNQizjBnRx0o96g3gQ,6117 +robot/running/dynamicmethods.py,sha256=lLNfVxQ_Uzg1mcHnAydUUjgA6iOnBAjMIri0vjrGZiw,5676 +robot/running/handlers.py,sha256=y_65n39ylFu6gCEsHpH_P_7SN0oXivctm31MtHBPNfI,13080 +robot/running/handlerstore.py,sha256=8NenhNJvqjSgUqsm1_Cgz-VrYPPWx2mTpU_yaHcgNhA,3120 +robot/running/importer.py,sha256=VdPQOem7WK8EeFyS3e83qHJRlo5PhEAE_vMoTcyAzCc,6108 +robot/running/librarykeywordrunner.py,sha256=Kv7mGSwN2Ydsrv_FcoNTV5iLCedjHDC5fIUs0ZydIFc,9085 +robot/running/libraryscopes.py,sha256=XM3gOjI6p211aEDZQtdq1FBjjS-FiG8BParxhU8GEeQ,2617 +robot/running/model.py,sha256=_0uyMeOPDzLcxMGX-JBy94co-vuiT4hNhoxiz6dkipY,14159 +robot/running/namespace.py,sha256=P2oehBSe7gsz3bOKwi_R5ieGnHhuJw7xRiP-WhKBLGg,17633 +robot/running/outputcapture.py,sha256=irXF2iTstc-qwExU6l6Lg5-jzPBDPeMQlcAkAv2G_xs,4543 +robot/running/randomizer.py,sha256=v5OJ_Ax8D8cm0LRiSmlB7V02-05yiYaw4-GakxhwXco,1927 +robot/running/runkwregister.py,sha256=9qqOe8a-vtgSHceMe_s0gtI7DoljBZAg4sMMozqp7QE,2562 +robot/running/runner.py,sha256=Oy2E_bJSoclK_Uh7eb_PRHX64S1SDmknUYEun7w0qGk,9640 +robot/running/signalhandler.py,sha256=nw8mhwS_FOiHXtb_PkV5F1XwF-Zc_Xbvcb9U4ZfXw0U,3338 +robot/running/status.py,sha256=SiXFFA32RLp69rpxaaURRmX1Y75AaqjAkabNLICgD6k,7625 +robot/running/statusreporter.py,sha256=oygXddaa7YA1PL97460Ns09fZUfHSdK0YUzKbqRriCk,3064 +robot/running/steprunner.py,sha256=e5u7IIS6BveEivtkDkVJxt8WDg7bcPFO80w9r7TKPY0,12488 +robot/running/testlibraries.py,sha256=9Ds3Ol7gt1fNJL_dQgMnIV92_ls2rFGYOcjNwXYN0bY,16255 +robot/running/timeouts/__init__.py,sha256=NzsUFtBeKbn53nfBLQ4FfqdEcu4KbxqAwiGBX4DJDbg,4210 +robot/running/timeouts/__pycache__/__init__.cpython-38.pyc,, +robot/running/timeouts/__pycache__/ironpython.cpython-38.pyc,, +robot/running/timeouts/__pycache__/jython.cpython-38.pyc,, +robot/running/timeouts/__pycache__/posix.cpython-38.pyc,, +robot/running/timeouts/__pycache__/windows.cpython-38.pyc,, +robot/running/timeouts/ironpython.py,sha256=uPgYxHfGm1F9ER0mMaY7jkbEorReQerb2Em_c_PcOjE,1817 +robot/running/timeouts/jython.py,sha256=jylFs4SRktulbw_kIBYhqeOVkyEtuSmmdrFblQXAvGs,1758 +robot/running/timeouts/posix.py,sha256=DrBB25De8uvD8K-OhZez9qF9WmeRCRvKG_upTPaVK5g,1248 +robot/running/timeouts/windows.py,sha256=GhmFCeJKg_c06WnCyVJcgfZxPZPj3DMsgGJh33O_Xaw,2328 +robot/running/usererrorhandler.py,sha256=u8uXzHw4Lou3TRzuC-wob6AXrv0XYZX5d3gMKQl0d4A,2341 +robot/running/userkeyword.py,sha256=pEQyxVMD-vXpFEPRYveP0nt8Dh4tiZkEvapS2uSxqpQ,4137 +robot/running/userkeywordrunner.py,sha256=DlUxrn0JIGx10BHrSw6ohVnCl1BaUv-TDDEhDXDPTxY,9950 +robot/testdoc.py,sha256=NnRpYR5mbI4Yjy9tB1hMgRpWYu7ARmvY_vzpbZuAZm4,10628 +robot/tidy.py,sha256=4iWTQkHNR753wJH93ojg0jDsdQHuZZ8kMsomf1Yotsg,10105 +robot/tidypkg/__init__.py,sha256=HnMzF13GN6bcgUUznOoUWhOcScY7d_UKTbr2AknrK5I,727 +robot/tidypkg/__pycache__/__init__.cpython-38.pyc,, +robot/tidypkg/__pycache__/transformers.cpython-38.pyc,, +robot/tidypkg/transformers.py,sha256=Kt2qEkGc45OX9EwdbBhAW_WtsSQ5hf1oeeLchIYbzwc,15598 +robot/utils/__init__.py,sha256=1l4yo2qR09S_mGQ-J1mkKE7xJRonzpHHAUHxY8NSV2k,3719 +robot/utils/__pycache__/__init__.cpython-38.pyc,, +robot/utils/__pycache__/application.cpython-38.pyc,, +robot/utils/__pycache__/argumentparser.cpython-38.pyc,, +robot/utils/__pycache__/asserts.cpython-38.pyc,, +robot/utils/__pycache__/charwidth.cpython-38.pyc,, +robot/utils/__pycache__/compat.cpython-38.pyc,, +robot/utils/__pycache__/compress.cpython-38.pyc,, +robot/utils/__pycache__/connectioncache.cpython-38.pyc,, +robot/utils/__pycache__/dotdict.cpython-38.pyc,, +robot/utils/__pycache__/encoding.cpython-38.pyc,, +robot/utils/__pycache__/encodingsniffer.cpython-38.pyc,, +robot/utils/__pycache__/error.cpython-38.pyc,, +robot/utils/__pycache__/escaping.cpython-38.pyc,, +robot/utils/__pycache__/etreewrapper.cpython-38.pyc,, +robot/utils/__pycache__/filereader.cpython-38.pyc,, +robot/utils/__pycache__/frange.cpython-38.pyc,, +robot/utils/__pycache__/htmlformatters.cpython-38.pyc,, +robot/utils/__pycache__/importer.cpython-38.pyc,, +robot/utils/__pycache__/markuputils.cpython-38.pyc,, +robot/utils/__pycache__/markupwriters.cpython-38.pyc,, +robot/utils/__pycache__/match.cpython-38.pyc,, +robot/utils/__pycache__/misc.cpython-38.pyc,, +robot/utils/__pycache__/normalizing.cpython-38.pyc,, +robot/utils/__pycache__/platform.cpython-38.pyc,, +robot/utils/__pycache__/recommendations.cpython-38.pyc,, +robot/utils/__pycache__/restreader.cpython-38.pyc,, +robot/utils/__pycache__/robotenv.cpython-38.pyc,, +robot/utils/__pycache__/robotinspect.cpython-38.pyc,, +robot/utils/__pycache__/robotio.cpython-38.pyc,, +robot/utils/__pycache__/robotpath.cpython-38.pyc,, +robot/utils/__pycache__/robottime.cpython-38.pyc,, +robot/utils/__pycache__/robottypes.cpython-38.pyc,, +robot/utils/__pycache__/robottypes2.cpython-38.pyc,, +robot/utils/__pycache__/robottypes3.cpython-38.pyc,, +robot/utils/__pycache__/setter.cpython-38.pyc,, +robot/utils/__pycache__/sortable.cpython-38.pyc,, +robot/utils/__pycache__/text.cpython-38.pyc,, +robot/utils/__pycache__/unic.cpython-38.pyc,, +robot/utils/application.py,sha256=2CLIe1FsPHVdKcQPzDjF1jE3yA22OCZ6gvV5rl894Kk,4355 +robot/utils/argumentparser.py,sha256=LqWhMFYYrp6xGO0Zq-zZkT3DSb4JlAvtnPH7iSqvs2s,15706 +robot/utils/asserts.py,sha256=kgj0Nl7VcM8XVRsgRdg8Gaygfvn4F_tDy7_dqa1Whnk,8812 +robot/utils/charwidth.py,sha256=PHWmVRB4eCPkGemf_6NyXYHn9pI9M6DrwOkh1-gjOfA,7460 +robot/utils/compat.py,sha256=lnzjpMvWdKloE6G7bIukE6aG7o-zP2vxok0CeDb4eOo,2675 +robot/utils/compress.py,sha256=n8o8JplZJUGOGxBTAEMBhu99mAQS3gZ7xtcvNfJjXd4,1602 +robot/utils/connectioncache.py,sha256=jSs05LwAW-VxRSv9a897tnBeiYT4GxFOCO9UpgnFIPc,6557 +robot/utils/dotdict.py,sha256=RLUPFhpY88eZcvaWQL_9pKGGLwZ2zcpe2uP3jA8b3wg,2317 +robot/utils/encoding.py,sha256=8PFGCfXeGv-wrrd9MU5xUQcPManqTMAtC3qYjP-rs8s,4145 +robot/utils/encodingsniffer.py,sha256=LIBarJ1_RKf8_miUfFeQ24yLjPhqQ9DDxyXkRpNuHlo,4213 +robot/utils/error.py,sha256=1kRZ9UpAQqkSstnZmladt7YGJh4cTtKIBqduhGIwjpQ,8240 +robot/utils/escaping.py,sha256=FlzcJ6cnGYVrq91hJLOksTIkGmmKaopve4cr7Ua_yQ4,4504 +robot/utils/etreewrapper.py,sha256=2cy9ziGFF6981Kob2ewXjzfz_Ogstc1b4_ySuD0gqSc,3641 +robot/utils/filereader.py,sha256=nDiAJQL9CuKzmDOpixVB2u9Nv3sbUiAcrTQ7BRdN-8c,3702 +robot/utils/frange.py,sha256=2APqtFvyDQsXgOrXzKMaZ-CiiJ2DCgvOuSoPud7mgeQ,2066 +robot/utils/htmlformatters.py,sha256=EEgsNglNueVIyFC-Sb9lOCploJZxFjEcOhlVPBTonBo,9866 +robot/utils/importer.py,sha256=tGTnBez6iEL0PmgP78RAYCvMliLf0PYNZ-HmkgKpm6Y,12111 +robot/utils/markuputils.py,sha256=5Rk-eA-hob0RKGTBEQ0Eat--1mffTbgx40fWyOFJTY4,1637 +robot/utils/markupwriters.py,sha256=VXQ3_uktSENd5P6gzzhKi6NMOgxKUx-nQu1DSOrdsEA,3547 +robot/utils/match.py,sha256=dRdb-y_-zi3fXu8J3-SdvAyOiVK6kwBU1SlPv4M5mzU,3105 +robot/utils/misc.py,sha256=C01P5WWXE1rMdfc0NeJd7cVSp1PgCGoWuZzzpvfoXac,4222 +robot/utils/normalizing.py,sha256=I6nZfkwKUoM0KDIH4pcRfk6k6oOmzbS63Hte00REEmk,4301 +robot/utils/platform.py,sha256=xFzq_6vSKkj9XZ_LXMdW0iHm05hMo0yQk6TJW8jDo8U,1238 +robot/utils/recommendations.py,sha256=VEZlmUslSmDXJkCIIBovixQHSq_FjwxJ6UxJRjl6f5M,3089 +robot/utils/restreader.py,sha256=k2iWiikzXZYcecLVBRvakVjXraEVrapr1LThGHDZI0I,2091 +robot/utils/robotenv.py,sha256=t2bPx-i7saWUyoxbQKu5ZXG-Mkda9QmJZ9Lmm53lZbE,1363 +robot/utils/robotinspect.py,sha256=vgY-htI9_D4lNYerDj-71oyiqjcAI6rC-eOhlvcBinY,1117 +robot/utils/robotio.py,sha256=pIPGyv3RGjxyx0CKxusR-3Rpbk1BZoxFoCmsgrVfsiw,2800 +robot/utils/robotpath.py,sha256=64LnRnxY_EhB9TLQWUnDK7YcHLiZtBLxXQlvyzjqzfU,6182 +robot/utils/robottime.py,sha256=B_nFLvy-H44XQQGlwMDmW7hBRi-gVApNHlnP1F6NW5U,14803 +robot/utils/robottypes.py,sha256=IH55HZ_7d0N9t4LJeat-De0Fy8nLDFmKENTcAD6Lf94,2132 +robot/utils/robottypes2.py,sha256=4yfM3iZjbCIdBpcncAGjRrU1ubeuqLlePnmeBIv2prE,2263 +robot/utils/robottypes3.py,sha256=9AwyQvGF6uQ7nOsc5PXZIP8a6vuC0K2aos_r0p5cSTw,2101 +robot/utils/setter.py,sha256=YWy8NjmMwmz2DK0OFhQqZQt2b3Ub_3dy197x9ecCfgQ,1551 +robot/utils/sortable.py,sha256=DQkMOHlLnS5Ofl5qsLUhOPJ_tXO_XWIqnkkUcWN7xCU,1657 +robot/utils/text.py,sha256=CisPiwQe21P6IgyvPyciEINAG_XuCef2XIYnq0PiEqA,5471 +robot/utils/unic.py,sha256=jDwrHbIssKovy9fNq2p5z1rxPN1uiEgLAeZPkqRgVkU,3262 +robot/variables/__init__.py,sha256=WwFM9hC-90oVPGWtEJInBYKBk82w19TZI3vtugvkkto,2565 +robot/variables/__pycache__/__init__.cpython-38.pyc,, +robot/variables/__pycache__/assigner.cpython-38.pyc,, +robot/variables/__pycache__/evaluation.cpython-38.pyc,, +robot/variables/__pycache__/filesetter.cpython-38.pyc,, +robot/variables/__pycache__/finders.cpython-38.pyc,, +robot/variables/__pycache__/notfound.cpython-38.pyc,, +robot/variables/__pycache__/replacer.cpython-38.pyc,, +robot/variables/__pycache__/scopes.cpython-38.pyc,, +robot/variables/__pycache__/search.cpython-38.pyc,, +robot/variables/__pycache__/store.cpython-38.pyc,, +robot/variables/__pycache__/tablesetter.cpython-38.pyc,, +robot/variables/__pycache__/variables.cpython-38.pyc,, +robot/variables/assigner.py,sha256=vGY9gbW6_2ugPVW6zduA7I1z2UM6uAMwxlSyAuvgAjM,8840 +robot/variables/evaluation.py,sha256=uWNPSaAlVn4To03wh-x_KVuvc1t7-OWQ-ce4FnZcKjY,4778 +robot/variables/filesetter.py,sha256=g4o_Bep7bf_c1RCMyt2nuR7VMbMG-yXbEZs-nYvYEt0,5747 +robot/variables/finders.py,sha256=U6Fa9-1pJHiTgpOcCC5MG2KaBLOu_i1V1uEd1v56E4U,6299 +robot/variables/notfound.py,sha256=IO4eKE4LMsN7hWHN2eEFUl6GmCcgNWzXUikWiA18Jbs,1756 +robot/variables/replacer.py,sha256=xZrP-9r-5vdzVW0iokEq9CCsiFtFtq3RuFkdmBFJk2M,8106 +robot/variables/scopes.py,sha256=BstCD66NFF3LMhmLT3Ci8wGHgQxsIq3kBZsi5nxmkIY,8623 +robot/variables/search.py,sha256=ZkrCEZ161XJyKvRE6Gz6nP-7UD0N2NUUI05wifeOy7I,10973 +robot/variables/store.py,sha256=bWLEHX1uin5BGFIz67Azqk6EPbbLwBhiC5KiVY_OpLk,4054 +robot/variables/tablesetter.py,sha256=Sr9sM2c4NcURfg0wMwROIqEYwMXxXvTnSC0XuJi8C0Q,5325 +robot/variables/variables.py,sha256=3QM0YNr7pPfhFMCJf6V7eyfYW4JeQTvfoZD1onX0VNk,2920 +robot/version.py,sha256=1KosJmJhAUKucpA9W1EWZHn7ENowgoDYPQczkWMFiw4,1527 +robotframework-3.2.2.dist-info/AUTHORS.rst,sha256=wE05tizdC-Xzl_eO7fO227GVc-oRkAx-gmNC9zxpidE,6417 +robotframework-3.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +robotframework-3.2.2.dist-info/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +robotframework-3.2.2.dist-info/METADATA,sha256=Kxuv2ESMfECnbzFhXYVeV5s5znRYZkuNBGyLu5yYL8Q,7537 +robotframework-3.2.2.dist-info/RECORD,, +robotframework-3.2.2.dist-info/WHEEL,sha256=ADKeyaGyKF5DwBNE0sRE5pvW-bSkFMJfBuhzZ3rceP4,110 +robotframework-3.2.2.dist-info/entry_points.txt,sha256=_0M6duBrFlHfnfb_oLZPiSxre_tmRaG6gmX8MhgT1Hs,75 +robotframework-3.2.2.dist-info/top_level.txt,sha256=tMJC4uYUpAuLPJb6Li-_mb8stZAN7xHqj1hBF2RwTUE,6 diff --git a/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/WHEEL b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..6d38aa0601b31c7f4c47ff3016173426df4e1d53 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.35.1) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/entry_points.txt b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..21c28807cc53ed4951ae6e32c67d48370d2072c1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/entry_points.txt @@ -0,0 +1,4 @@ +[console_scripts] +rebot = robot.rebot:rebot_cli +robot = robot.run:run_cli + diff --git a/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..ba639253ded2d4f9ad9077d46d1c77acc89c1210 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework-3.2.2.dist-info/top_level.txt @@ -0,0 +1 @@ +robot diff --git a/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/METADATA b/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..8d5cd2adc7d0d08ddb6ade476ba1863b1b62c544 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/METADATA @@ -0,0 +1,31 @@ +Metadata-Version: 2.1 +Name: robotframework-httpctrl +Version: 0.1.6 +Summary: robotframework-httpctrl is a library for Robot Framework that provides HTTP/HTTPS client and HTTP server services +Home-page: https://github.com/annoviko/robotframework-httpctrl +Author: Andrei Novikov +Author-email: spb.andr@yandex.ru +License: GNU Public License +Project-URL: Homepage, https://annoviko.github.io/robotframework-httpctrl/ +Project-URL: Repository, https://github.com/annoviko/robotframework-httpctrl +Project-URL: Documentation, https://annoviko.github.io/robotframework-httpctrl/ +Project-URL: Bug Tracker, https://github.com/annoviko/robotframework-httpctrl/issues +Keywords: httpctrl http https robotframework client server json test testing +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Telecommunications Industry +Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) +Classifier: Natural Language :: English +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Topic :: Education +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers +Classifier: Topic :: Software Development :: Testing +Classifier: Framework :: Robot Framework :: Library +Requires-Python: >=3.4 +Requires-Dist: robotframework + +robotframework-httpctrl is a library for Robot Framework that provides HTTP/HTTPS client and HTTP server services. + + diff --git a/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/RECORD b/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..a437bc54bfee9c6625276ab0c848f99635d01b0c --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/RECORD @@ -0,0 +1,25 @@ +HttpCtrl/__init__.py,sha256=koYTKievSHU8RvZ2at90w6o_mUmk2brSHBx3mkS8ugc,39464 +HttpCtrl/__pycache__/__init__.cpython-38.pyc,, +HttpCtrl/__pycache__/http_handler.cpython-38.pyc,, +HttpCtrl/__pycache__/http_server.cpython-38.pyc,, +HttpCtrl/__pycache__/internal_messages.cpython-38.pyc,, +HttpCtrl/__pycache__/request.cpython-38.pyc,, +HttpCtrl/__pycache__/request_storage.cpython-38.pyc,, +HttpCtrl/__pycache__/response.cpython-38.pyc,, +HttpCtrl/__pycache__/response_storage.cpython-38.pyc,, +HttpCtrl/http_handler.py,sha256=I04w7z02FwSQocSxVJJUomfCApNJhlfFCNnCMGVJOb0,3636 +HttpCtrl/http_server.py,sha256=86T5a04MZ2TqlVUaRd_C7IXnYgSKPDW-10LsxQRUlJw,2324 +HttpCtrl/internal_messages.py,sha256=98FCT7t3PWSwLq0ZzYasaf5msUUW_Ut64bEY8gQpmYw,894 +HttpCtrl/request.py,sha256=Tr9gOpjjGbho_97UMavKTewNHISkTosmXSVjX8MxEFg,1600 +HttpCtrl/request_storage.py,sha256=npV_z9FqT5EVw-ZQg34td4nhpAc-peWvF37KS6CN2Q8,1841 +HttpCtrl/response.py,sha256=l_FxbfYlLielktQGAykDyVcOBWc7_-lmscV_qCYl2dk,1435 +HttpCtrl/response_storage.py,sha256=lZ1D2ptfdioEQvOGWrb40-viyijh5aaQukn5Co8dNYk,1855 +HttpCtrl/utils/__init__.py,sha256=RIaBcyk9P6Ce27EQ_fQAmvDYcHd_42-HfP9wrGd2Hqc,817 +HttpCtrl/utils/__pycache__/__init__.cpython-38.pyc,, +HttpCtrl/utils/__pycache__/singleton.cpython-38.pyc,, +HttpCtrl/utils/singleton.py,sha256=VWjeueGOzDABy8FPsGKitWPTn6fKxlI8JrKpLEE-fug,1063 +robotframework_httpctrl-0.1.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +robotframework_httpctrl-0.1.6.dist-info/METADATA,sha256=d8x3bgswKqFEZi9M5_57L1UlA-tnD2rtHl1ewqYoPnA,1486 +robotframework_httpctrl-0.1.6.dist-info/RECORD,, +robotframework_httpctrl-0.1.6.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92 +robotframework_httpctrl-0.1.6.dist-info/top_level.txt,sha256=oTz_CpDhfH_0g9ssjNAGp5Yypn8e7C-SIXK-shxR-Xk,9 diff --git a/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/WHEEL b/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..b552003ff90e66227ec90d1b159324f140d46001 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..f64ac6ecd64e2c64581e4ebd166f2fe67a5ee9ae --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_httpctrl-0.1.6.dist-info/top_level.txt @@ -0,0 +1 @@ +HttpCtrl diff --git a/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/LICENSE b/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..818433ecc0e094a4db1023c68b33f24344643ad8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/METADATA b/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..9bc9d5e195be1be8f13f697d4458c894bfe2c171 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/METADATA @@ -0,0 +1,80 @@ +Metadata-Version: 2.1 +Name: robotframework-httpd +Version: 0.8.2 +Summary: Robot Framework HTTPD Simulator +Home-page: https://github.com/mbbn/robotframework-httpd +Author: Mohammad Biabani +Author-email: biabani.mohammad@gmail.com +License: MIT +Download-URL: https://pypi.python.org/pypi/robotframework-httpd +Keywords: robotframework testing test automation testautomation atdd bdd httpd +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 2.7 +Classifier: Topic :: Software Development :: Testing +Classifier: Topic :: Software Development :: Quality Assurance + +robotframework-httpd +==================== + +Robot Framework keyword library for HTTPD Simulator. + +This module allows easy to create http server and test request that http server is received + + + +Installation +------------ + +``pip install robotframework-httpd`` + +Usage +----- +`HTTPDLibrary keyword +documentation `__ + +.. code:: robotframework + + *** Settings *** + Library HTTPDLibrary port=5060 + Library RequestsLibrary + Library Collections + + *** Test Cases *** + Test HttpdLibrary GET + Get Request /test?param1=p1 + Run Httpd + + Create Session Httpd http://localhost:5060 + ${resp}= Get Httpd /test?param1=p1 + + wait to get request + + Test HttpdLibrary Post + Post Request this is body + Run Httpd + + Create Session Httpd http://localhost:5060 + ${resp}= Post Httpd / data=this is body + + wait to get request + + *** Keywords *** + Get Request + [Arguments] ${path} + ${request}= create dictionary method GET path ${path} + set wished request ${request} + + Post Request + [Arguments] ${post_body} + ${request}= create dictionary method POST post_body ${post_body} + set wished request ${request} + +Contribute +---------- + +If you like this module, please contribute! I welcome patches, +documentation, issues, ideas, and so on. + diff --git a/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/RECORD b/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..bcc4e1025cc51feb752beb60fc266b10872b184e --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/RECORD @@ -0,0 +1,8 @@ +HTTPDLibrary/__init__.py,sha256=U-lC-fB0HHsiZRvH65FEOMSIfovU1Mh6yAhcSroe4VI,7319 +HTTPDLibrary/__pycache__/__init__.cpython-38.pyc,, +robotframework_httpd-0.8.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +robotframework_httpd-0.8.2.dist-info/LICENSE,sha256=Czg9WmPaZE9ijZnDOXbqZIftiaqlnwsyV5kt6sEXHms,35821 +robotframework_httpd-0.8.2.dist-info/METADATA,sha256=jNbzSEYLNlaYJWW5ubuQR0nAJHlhmI_Q8NwmjBXi6Q0,2180 +robotframework_httpd-0.8.2.dist-info/RECORD,, +robotframework_httpd-0.8.2.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92 +robotframework_httpd-0.8.2.dist-info/top_level.txt,sha256=-41DeL-Zc90fQ1M2JD4Dbq2fAEQI8_AY-ZTIC2cYvAI,13 diff --git a/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/WHEEL b/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..b552003ff90e66227ec90d1b159324f140d46001 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..e87501e52a1b449b66ebd7fe875128e30f9f08e7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_httpd-0.8.2.dist-info/top_level.txt @@ -0,0 +1 @@ +HTTPDLibrary diff --git a/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/METADATA b/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..3727ce8d24cc6d03ccae1042f22811e37ac7506b --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/METADATA @@ -0,0 +1,24 @@ +Metadata-Version: 2.1 +Name: robotframework-jsonlibrary +Version: 0.3.1 +Summary: robotframework-jsonlibrary is a Robot Framework test library for manipulating JSON Object. You can manipulate your JSON object using JSONPath +Home-page: https://github.com/nottyo/robotframework-jsonlibrary.git +Author: Traitanit Huangsri +Author-email: traitanit.hua@gmail.com +License: UNKNOWN +Keywords: robotframework-jsonlibrary +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: Public Domain +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Testing +Requires-Dist: coverage +Requires-Dist: jsonpath-rw-ext (>=0.1.9) +Requires-Dist: jsonpath-rw (==1.4.0) +Requires-Dist: robotframework (>=3.0) +Requires-Dist: tox (==3.0.0) + +UNKNOWN + + diff --git a/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/RECORD b/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..4cc7303dc0206d6eb08714411e290e791c414554 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/RECORD @@ -0,0 +1,11 @@ +JSONLibrary/JSONLibraryKeywords.py,sha256=lT-XQPuNXai7Yi7IHWUVsC8sexv2M-_L5NfMST5Ldco,4972 +JSONLibrary/__init__.py,sha256=0chfO0R29O_jROEQkkp2kbmmAJKhsapYTdbyA1DhoNM,2272 +JSONLibrary/__pycache__/JSONLibraryKeywords.cpython-38.pyc,, +JSONLibrary/__pycache__/__init__.cpython-38.pyc,, +JSONLibrary/__pycache__/version.cpython-38.pyc,, +JSONLibrary/version.py,sha256=V2MESUKai6ClLT2aY-GwP2hbxiWCt-y9TA4TxrGf_24,65 +robotframework_jsonlibrary-0.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +robotframework_jsonlibrary-0.3.1.dist-info/METADATA,sha256=etPKuGwmIUuud3EFrCdmtpISVlwfXJj0jpsy8BA-wnU,842 +robotframework_jsonlibrary-0.3.1.dist-info/RECORD,, +robotframework_jsonlibrary-0.3.1.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92 +robotframework_jsonlibrary-0.3.1.dist-info/top_level.txt,sha256=StlngaZLa4vh9u1VfSJ1c_38VBV0EtlGH36jdH2_heg,12 diff --git a/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/WHEEL b/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..b552003ff90e66227ec90d1b159324f140d46001 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..927693bd78dad027dbea7a2d307c3a980d1decb2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_jsonlibrary-0.3.1.dist-info/top_level.txt @@ -0,0 +1 @@ +JSONLibrary diff --git a/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/METADATA b/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..9c70cfc0db7f5d43eb3928a1c7dd04cb9236e9da --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/METADATA @@ -0,0 +1,59 @@ +Metadata-Version: 2.1 +Name: robotframework-jsonschemalibrary +Version: 1.0 +Summary: A Robot Framework library for JSON Schema validation. +Home-page: https://github.com/jstaffans +Author: Johannes Staffans +Author-email: UNKNOWN +License: Apache License 2.0 +Keywords: robotframework test json schema +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Environment :: Web Environment +Classifier: Framework :: Robot Framework +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: Implementation :: CPython +Requires-Dist: jsonschema +Requires-Dist: robotframework + +==================================================== +A Robot Framework library for JSON Schema validation +==================================================== + +Provides a simple interface to `jsonschema`_, the Python implementation of JSON Schema. + +Usage +----- + +The library needs access to the file system location of the schemas, in order to resolve references +between schemas. Default is a subdirectory called `schemas` - you could make a symlink here to wherever +the schema definition files are:: + + Library JSONSchemaLibrary /path/to/schemas + ... + My Test Case: + Validate Json schema_name.schema.json {"foo": "bar"} + +Per default, only prints the validation error message when there's an error. +Run with log level `DEBUG` in order to see more info, including a dump of the schema, in the Robot Framework logs. + +Development +----------- + +:: + + $ pybot --pythonpath . tests + +Todo +---- + +* HTTP resolver + +.. _`jsonschema`: https://github.com/Julian/jsonschema + + + + + diff --git a/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/RECORD b/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..c9371dd60d57b246d8a1daf6f4c9f58e1a64a105 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/RECORD @@ -0,0 +1,8 @@ +JSONSchemaLibrary/__init__.py,sha256=35jZvNYfN5i_dRIw63IJoyQcJTS11d3ybVCWgW2lmAk,1477 +JSONSchemaLibrary/__pycache__/__init__.cpython-38.pyc,, +robotframework_jsonschemalibrary-1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +robotframework_jsonschemalibrary-1.0.dist-info/METADATA,sha256=zoJfBWgroNEJJvopH9MGuj9KlvcffTDlItovg0XTkvA,1720 +robotframework_jsonschemalibrary-1.0.dist-info/RECORD,, +robotframework_jsonschemalibrary-1.0.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92 +robotframework_jsonschemalibrary-1.0.dist-info/entry_points.txt,sha256=wqNxVlvawq3Rri3WwdPiHW4dRNTRo5iOFz5s8o8zqsA,33 +robotframework_jsonschemalibrary-1.0.dist-info/top_level.txt,sha256=E6xVIKwDfkbk-fAlBkKbIvXlLoI-3LbJcdyWHJoPA-s,18 diff --git a/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/WHEEL b/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..b552003ff90e66227ec90d1b159324f140d46001 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/entry_points.txt b/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..a184cd05daa256c9484e1ecc4817fa61517e8bc9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/entry_points.txt @@ -0,0 +1,3 @@ + + # -*- Entry points: -*- + \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..a363ea1b2af717210cb6d16d14142754cea24909 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_jsonschemalibrary-1.0.dist-info/top_level.txt @@ -0,0 +1 @@ +JSONSchemaLibrary diff --git a/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/LICENSE.md b/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..c1b27b217e10b873cd4e375427df68f056b1f146 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/LICENSE.md @@ -0,0 +1,25 @@ +Copyright (c) 2016 Bulkan Evcimen + + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + + + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + + + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/METADATA b/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..04a6483dbbb173cdf2eb7d3b24bf2d6cc9d33615 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/METADATA @@ -0,0 +1,30 @@ +Metadata-Version: 2.1 +Name: robotframework-requests +Version: 0.8.0 +Summary: Robot Framework keyword library wrapper around requests +Home-page: http://github.com/bulkan/robotframework-requests +Author: Bulkan Savun Evcimen +Author-email: bulkan@gmail.com +Maintainer: Luca Giovenzana +Maintainer-email: luca@giovenzana.org +License: MIT +Keywords: robotframework testing test automation http client requests +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Testing +Requires-Dist: robotframework +Requires-Dist: requests +Provides-Extra: test +Requires-Dist: robotframework (>=3.2.1) ; extra == 'test' +Requires-Dist: pytest ; extra == 'test' +Requires-Dist: flask ; extra == 'test' +Requires-Dist: six ; extra == 'test' +Requires-Dist: coverage ; extra == 'test' +Requires-Dist: flake8 ; extra == 'test' + +Robot Framework keyword library wrapper around the HTTP client library requests. + + diff --git a/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/RECORD b/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..c28469adc574fc6770a639efc3eda888fe53ce6f --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/RECORD @@ -0,0 +1,26 @@ +RequestsLibrary/DeprecatedKeywords.py,sha256=BEBtUCEm4wtMIKcDxh26d3YbH9xugWGtRbL5xOE8ve0,11932 +RequestsLibrary/RequestsKeywords.py,sha256=k_BI5-K5lKO5M4OZqU0iRlbwaSXKijoG1Viw1jMgc10,2633 +RequestsLibrary/RequestsOnSessionKeywords.py,sha256=yneYRNVQDz3Ewd0GOxFxGEprbszOqDU5vJZp5c1IRN0,11338 +RequestsLibrary/SessionKeywords.py,sha256=hYJYyAIVlH0qNc5ZF0c-gyUt01_qTaMdbrrQ-ZttrHw,26487 +RequestsLibrary/__init__.py,sha256=_Xq30-_Q7dy_MLD-d2nK2g60flnOzjDCl3jS7bZf1-E,4750 +RequestsLibrary/__pycache__/DeprecatedKeywords.cpython-38.pyc,, +RequestsLibrary/__pycache__/RequestsKeywords.cpython-38.pyc,, +RequestsLibrary/__pycache__/RequestsOnSessionKeywords.cpython-38.pyc,, +RequestsLibrary/__pycache__/SessionKeywords.cpython-38.pyc,, +RequestsLibrary/__pycache__/__init__.cpython-38.pyc,, +RequestsLibrary/__pycache__/compat.cpython-38.pyc,, +RequestsLibrary/__pycache__/exceptions.cpython-38.pyc,, +RequestsLibrary/__pycache__/log.cpython-38.pyc,, +RequestsLibrary/__pycache__/utils.cpython-38.pyc,, +RequestsLibrary/__pycache__/version.cpython-38.pyc,, +RequestsLibrary/compat.py,sha256=HO33oZAEwyl4E67zuG6-u625poR-9AWndPlJKHEAW5U,871 +RequestsLibrary/exceptions.py,sha256=ir4voSQwV222QvePuW7LqiCViwjMAyzY4MaxHX4xX3I,142 +RequestsLibrary/log.py,sha256=WM0orhix-4_BFjYBBTBUoYR9cXxGRQB2IVZW-ns_Fcg,1485 +RequestsLibrary/utils.py,sha256=Knv0Jt11GeFm9xy8-FB2AMzjsRNY2GrqV3GPysq1txs,3676 +RequestsLibrary/version.py,sha256=_3ckymnti-ag6fZMN2F_byc65z5fY0X2q1Xonebkvgs,18 +robotframework_requests-0.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +robotframework_requests-0.8.0.dist-info/LICENSE.md,sha256=Tm8r0Oi_jANvTtik19lla26S0jgpesDwIrX9023AOhs,1065 +robotframework_requests-0.8.0.dist-info/METADATA,sha256=1KwwVtHI1Kv18gZVsiqMzG8IvJSZuohbU_ftTYou6p8,1080 +robotframework_requests-0.8.0.dist-info/RECORD,, +robotframework_requests-0.8.0.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92 +robotframework_requests-0.8.0.dist-info/top_level.txt,sha256=RYslV7QCRF3eCOdeHkQXKEinIWqi6N57bivIBI2sppA,16 diff --git a/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/WHEEL b/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..385faab0525ccdbfd1070a8bebcca3ac8617236e --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.36.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..4af82a74805e98e1a6dd9e96dd5e15c9dbf4e469 --- /dev/null +++ b/robot/lib/python3.8/site-packages/robotframework_requests-0.8.0.dist-info/top_level.txt @@ -0,0 +1 @@ +RequestsLibrary diff --git a/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/AUTHORS.txt b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/AUTHORS.txt new file mode 100644 index 0000000000000000000000000000000000000000..72c87d7d38ae7bf859717c333a5ee8230f6ce624 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/AUTHORS.txt @@ -0,0 +1,562 @@ +A_Rog +Aakanksha Agrawal <11389424+rasponic@users.noreply.github.com> +Abhinav Sagar <40603139+abhinavsagar@users.noreply.github.com> +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Tse +Adam Wentz +admin +Adrien Morison +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Aleks Bunin +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Alexey Popravka +Alli +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrey Bulgakov +Andrés Delfino <34587441+andresdelfino@users.noreply.github.com> +Andrés Delfino +Andy Freeland +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anuj Godase +AQNOUCH Mohammed +AraHaan +Arindam Choudhury +Armin Ronacher +Artem +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avner Cohen +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Darnell +Ben Hoyt +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Brandon L. Reiss +Brandt Bucher +Brett Randall +Brian Cristante <33549821+brcrista@users.noreply.github.com> +Brian Cristante +Brian Rosner +BrownTruck +Bruno Oliveira +Bruno Renié +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +c22 +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Chih-Hsuan Yen +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris McDonough +Chris Wolfe +Christian Heimes +Christian Oudard +Christopher Hunt +Christopher Snyder +Clark Boylan +Clay McClure +Cody +Cody Soyland +Colin Watson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Curtis Doty +cytolentino +Damian Quiroga +Dan Black +Dan Savilonis +Dan Sully +daniel +Daniel Collins +Daniel Hahler +Daniel Holth +Daniel Jost +Daniel Shaulov +Daniele Esposti +Daniele Procida +Danny Hermes +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Bordeynik +David Caro +David Evans +David Linke +David Pursehouse +David Tucker +David Wales +Davidovich +derwolfe +Desetude +Diego Caraballo +DiegoCaraballo +Dmitry Gladkov +Domen Kožar +Donald Stufft +Dongweiming +Douglas Thor +DrFeathers +Dustin Ingram +Dwayne Bailey +Ed Morley <501702+edmorley@users.noreply.github.com> +Ed Morley +Eitan Adler +ekristina +elainechan +Eli Schwartz +Eli Schwartz +Emil Burzo +Emil Styrke +Endoh Takanao +enoch +Erdinc Mutlu +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Ernest W Durbin III +Ernest W. Durbin III +Erwin Janssen +Eugene Vereshchagin +everdimension +Felix Yan +fiber-space +Filip Kokosiński +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gdanielson +Geoffrey Lehée +Geoffrey Sneddon +George Song +Georgi Valkov +Giftlin Rajaiah +gizmoguy1 +gkdoc <40815324+gkdoc@users.noreply.github.com> +Gopinath M <31352222+mgopi1990@users.noreply.github.com> +GOTO Hayato <3532528+gh640@users.noreply.github.com> +gpiks +Guilherme Espada +Guy Rozendorn +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +Herbert Pfennig +Hsiaoming Yang +Hugo +Hugo Lopes Tavares +Hugo van Kemenade +hugovk +Hynek Schlawack +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ilya Baryshev +INADA Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Ivan Pozdeev +Jacob Kim +jakirkham +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James Cleveland +James Cleveland +James Firth +James Polley +Jan Pokorný +Jannis Leidel +jarondl +Jason R. Coombs +Jay Graves +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jelmer Vernooij +jenix21 +Jeremy Stanley +Jeremy Zafran +Jiashuo Li +Jim Garrison +Jivan Amara +John Paton +John-Scott Atlakson +johnthagen +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joost Molenaar +Jorge Niedbalski +Joseph Long +Josh Bronson +Josh Hansen +Josh Schneier +Juanjo Bazán +Julian Berman +Julian Gethmann +Julien Demoor +jwg4 +Jyrki Pulliainen +Kai Chen +Kamal Bin Mustafa +kaustav haldar +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kexuan Sun +Kit Randel +kpinc +Krishna Oza +Kumar McMillan +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurie Opperman +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +Loren Carvalho +Lucas Cimon +Ludovic Gasc +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +László Kiss Kollár +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark Williams +Mark Williams +Markus Hametner +Masaki +Masklinn +Matej Stuchlik +Mathew Jennings +Mathieu Bridon +Matt Good +Matt Maker +Matt Robenolt +matthew +Matthew Einhorn +Matthew Gilliard +Matthew Iversen +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna <44498973+mbaluna@users.noreply.github.com> +mdebi <17590103+mdebi@users.noreply.github.com> +memoselyk +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Williamson +michaelpacer +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Monica Baluna +montefra +Monty Taylor +Nate Coraor +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nikhil Benesch +Nitesh Sharma +Nowell Strite +NtaleGrey +nvdv +Ofekmeister +ofrinevo +Oliver Jeeves +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +OMOTO Kenji +Omry Yadan +Oren Held +Oscar Benjamin +Oz N Tiram +Pachwenko <32424503+Pachwenko@users.noreply.github.com> +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavithra Eswaramoorthy <33131404+QueenCoffee@users.noreply.github.com> +Pawel Jasinski +Pekka Klärck +Peter Lisák +Peter Waller +petr-tik +Phaneendra Chiruvella +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Pratik Mallya +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal <7895pulkit@gmail.com> +Qiangning Hong +Quentin Pradet +R. David Murray +Rafael Caricio +Ralf Schmitt +Razzi Abuissa +rdb +Remi Rampin +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Richard Jones +RobberPhex +Robert Collins +Robert McGibbon +Robert T. McGibbon +robin elisha robinson +Roey Berman +Rohan Jain +Rohan Jain +Rohan Jain +Roman Bogorodskiy +Romuald Brunet +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +Roy Wellington Ⅳ +Ryan Wooden +ryneeverett +Sachi King +Salvatore Rinchiera +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sergey Vasilyev +Seth Woodworth +Shlomi Fish +Shovan Maity +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +Sorin Sbarnea +Stavros Korokithakis +Stefan Scherfke +Stephan Erb +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +stonebig +Stéphane Bidoul (ACSONE) +Stéphane Bidoul +Stéphane Klein +Sumana Harihareswara +Sviatoslav Sydorenko +Sviatoslav Sydorenko +Swat009 +Takayuki SHIMIZUKAWA +tbeswick +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tom Forbes +Tom Freudenheim +Tom V +Tomas Orsava +Tomer Chachamu +Tony Beswick +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +Travis Swicegood +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Philippon +Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> +Vitaly Babiy +Vladimir Rutsky +W. Trevor King +Wil Tan +Wilfred Hughes +William ML Leslie +William T Olson +Wilson Mo +wim glenn +Wolfgang Maier +Xavier Fernandez +Xavier Fernandez +xoviat +xtreak +YAMAMOTO Takashi +Yen Chi Hsuan +Yeray Diaz Diaz +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Zearin +Zearin +Zhiping Deng +Zvezdan Petkovic +Łukasz Langa +Семён Марьясин diff --git a/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..737fec5c5352af3d9a6a47a0670da4bdb52c5725 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2019 The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/METADATA b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..4adf953086ea4e28c5236788234f38f88602296f --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/METADATA @@ -0,0 +1,82 @@ +Metadata-Version: 2.1 +Name: setuptools +Version: 44.0.0 +Summary: Easily download, build, install, upgrade, and uninstall Python packages +Home-page: https://github.com/pypa/setuptools +Author: Python Packaging Authority +Author-email: distutils-sig@python.org +License: UNKNOWN +Project-URL: Documentation, https://setuptools.readthedocs.io/ +Keywords: CPAN PyPI distutils eggs package management +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Archiving :: Packaging +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities +Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7 +Description-Content-Type: text/x-rst; charset=UTF-8 + +.. image:: https://img.shields.io/pypi/v/setuptools.svg + :target: https://pypi.org/project/setuptools + +.. image:: https://img.shields.io/readthedocs/setuptools/latest.svg + :target: https://setuptools.readthedocs.io + +.. image:: https://img.shields.io/travis/pypa/setuptools/master.svg?label=Linux%20CI&logo=travis&logoColor=white + :target: https://travis-ci.org/pypa/setuptools + +.. image:: https://img.shields.io/appveyor/ci/pypa/setuptools/master.svg?label=Windows%20CI&logo=appveyor&logoColor=white + :target: https://ci.appveyor.com/project/pypa/setuptools/branch/master + +.. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white + :target: https://codecov.io/gh/pypa/setuptools + +.. image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat + :target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme + +.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg + +See the `Installation Instructions +`_ in the Python Packaging +User's Guide for instructions on installing, upgrading, and uninstalling +Setuptools. + +Questions and comments should be directed to the `distutils-sig +mailing list `_. +Bug reports and especially tested patches may be +submitted directly to the `bug tracker +`_. + +To report a security vulnerability, please use the +`Tidelift security contact `_. +Tidelift will coordinate the fix and disclosure. + + +For Enterprise +============== + +Available as part of the Tidelift Subscription. + +Setuptools and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. + +`Learn more `_. + +Code of Conduct +=============== + +Everyone interacting in the setuptools project's codebases, issue trackers, +chat rooms, and mailing lists is expected to follow the +`PyPA Code of Conduct `_. + + diff --git a/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/RECORD b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..5a6f3e6b6c35fb2fa1357b00b65e5caed7bbf9a1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/RECORD @@ -0,0 +1,171 @@ +easy_install.py,sha256=MDC9vt5AxDsXX5qcKlBz2TnW6Tpuv_AobnfhCJ9X3PM,126 +setuptools/__init__.py,sha256=WBpCcn2lvdckotabeae1TTYonPOcgCIF3raD2zRWzBc,7283 +setuptools/_deprecation_warning.py,sha256=jU9-dtfv6cKmtQJOXN8nP1mm7gONw5kKEtiPtbwnZyI,218 +setuptools/_imp.py,sha256=jloslOkxrTKbobgemfP94YII0nhqiJzE1bRmCTZ1a5I,2223 +setuptools/archive_util.py,sha256=kw8Ib_lKjCcnPKNbS7h8HztRVK0d5RacU3r_KRdVnmM,6592 +setuptools/build_meta.py,sha256=-9Nmj9YdbW4zX3TssPJZhsENrTa4fw3k86Jm1cdKMik,9597 +setuptools/cli-32.exe,sha256=dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y,65536 +setuptools/cli-64.exe,sha256=KLABu5pyrnokJCv6skjXZ6GsXeyYHGcqOUT3oHI3Xpo,74752 +setuptools/cli.exe,sha256=dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y,65536 +setuptools/config.py,sha256=6SB2OY3qcooOJmG_rsK_s0pKBsorBlDpfMJUyzjQIGk,20575 +setuptools/dep_util.py,sha256=fgixvC1R7sH3r13ktyf7N0FALoqEXL1cBarmNpSEoWg,935 +setuptools/depends.py,sha256=qt2RWllArRvhnm8lxsyRpcthEZYp4GHQgREl1q0LkFw,5517 +setuptools/dist.py,sha256=xtXaNsOsE32MwwQqErzgXJF7jsTQz9GYFRrwnPFQ0J0,49865 +setuptools/errors.py,sha256=MVOcv381HNSajDgEUWzOQ4J6B5BHCBMSjHfaWcEwA1o,524 +setuptools/extension.py,sha256=uc6nHI-MxwmNCNPbUiBnybSyqhpJqjbhvOQ-emdvt_E,1729 +setuptools/glob.py,sha256=o75cHrOxYsvn854thSxE0x9k8JrKDuhP_rRXlVB00Q4,5084 +setuptools/gui-32.exe,sha256=XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA,65536 +setuptools/gui-64.exe,sha256=aYKMhX1IJLn4ULHgWX0sE0yREUt6B3TEHf_jOw6yNyE,75264 +setuptools/gui.exe,sha256=XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA,65536 +setuptools/installer.py,sha256=TCFRonRo01I79zo-ucf3Ymhj8TenPlmhMijN916aaJs,5337 +setuptools/launch.py,sha256=sd7ejwhBocCDx_wG9rIs0OaZ8HtmmFU8ZC6IR_S0Lvg,787 +setuptools/lib2to3_ex.py,sha256=t5e12hbR2pi9V4ezWDTB4JM-AISUnGOkmcnYHek3xjg,2013 +setuptools/monkey.py,sha256=FGc9fffh7gAxMLFmJs2DW_OYWpBjkdbNS2n14UAK4NA,5264 +setuptools/msvc.py,sha256=8baJ6aYgCA4TRdWQQi185qB9dnU8FaP4wgpbmd7VODs,46751 +setuptools/namespaces.py,sha256=F0Nrbv8KCT2OrO7rwa03om4N4GZKAlnce-rr-cgDQa8,3199 +setuptools/package_index.py,sha256=6pb-B1POtHyLycAbkDETk4fO-Qv8_sY-rjTXhUOoh6k,40605 +setuptools/py27compat.py,sha256=tvmer0Tn-wk_JummCkoM22UIjpjL-AQ8uUiOaqTs8sI,1496 +setuptools/py31compat.py,sha256=h2rtZghOfwoGYd8sQ0-auaKiF3TcL3qX0bX3VessqcE,838 +setuptools/py33compat.py,sha256=SMF9Z8wnGicTOkU1uRNwZ_kz5Z_bj29PUBbqdqeeNsc,1330 +setuptools/py34compat.py,sha256=KYOd6ybRxjBW8NJmYD8t_UyyVmysppFXqHpFLdslGXU,245 +setuptools/sandbox.py,sha256=9UbwfEL5QY436oMI1LtFWohhoZ-UzwHvGyZjUH_qhkw,14276 +setuptools/script (dev).tmpl,sha256=RUzQzCQUaXtwdLtYHWYbIQmOaES5Brqq1FvUA_tu-5I,218 +setuptools/script.tmpl,sha256=WGTt5piezO27c-Dbx6l5Q4T3Ff20A5z7872hv3aAhYY,138 +setuptools/site-patch.py,sha256=OumkIHMuoSenRSW1382kKWI1VAwxNE86E5W8iDd34FY,2302 +setuptools/ssl_support.py,sha256=nLjPUBBw7RTTx6O4RJZ5eAMGgjJG8beiDbkFXDZpLuM,8493 +setuptools/unicode_utils.py,sha256=NOiZ_5hD72A6w-4wVj8awHFM3n51Kmw1Ic_vx15XFqw,996 +setuptools/version.py,sha256=og_cuZQb0QI6ukKZFfZWPlr1HgJBPPn2vO2m_bI9ZTE,144 +setuptools/wheel.py,sha256=zct-SEj5_LoHg6XELt2cVRdulsUENenCdS1ekM7TlZA,8455 +setuptools/windows_support.py,sha256=5GrfqSP2-dLGJoZTq2g6dCKkyQxxa2n5IQiXlJCoYEE,714 +setuptools/_vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +setuptools/_vendor/ordered_set.py,sha256=dbaCcs27dyN9gnMWGF5nA_BrVn6Q-NrjKYJpV9_fgBs,15130 +setuptools/_vendor/pyparsing.py,sha256=tmrp-lu-qO1i75ZzIN5A12nKRRD1Cm4Vpk-5LR9rims,232055 +setuptools/_vendor/six.py,sha256=A6hdJZVjI3t_geebZ9BzUvwRrIXo0lfwzQlM2LcKyas,30098 +setuptools/_vendor/packaging/__about__.py,sha256=CpuMSyh1V7adw8QMjWKkY3LtdqRUkRX4MgJ6nF4stM0,744 +setuptools/_vendor/packaging/__init__.py,sha256=6enbp5XgRfjBjsI9-bn00HjHf5TH21PDMOKkJW8xw-w,562 +setuptools/_vendor/packaging/_compat.py,sha256=Ugdm-qcneSchW25JrtMIKgUxfEEBcCAz6WrEeXeqz9o,865 +setuptools/_vendor/packaging/_structures.py,sha256=pVd90XcXRGwpZRB_qdFuVEibhCHpX_bL5zYr9-N0mc8,1416 +setuptools/_vendor/packaging/markers.py,sha256=-meFl9Fr9V8rF5Rduzgett5EHK9wBYRUqssAV2pj0lw,8268 +setuptools/_vendor/packaging/requirements.py,sha256=3dwIJekt8RRGCUbgxX8reeAbgmZYjb0wcCRtmH63kxI,4742 +setuptools/_vendor/packaging/specifiers.py,sha256=0ZzQpcUnvrQ6LjR-mQRLzMr8G6hdRv-mY0VSf_amFtI,27778 +setuptools/_vendor/packaging/tags.py,sha256=EPLXhO6GTD7_oiWEO1U0l0PkfR8R_xivpMDHXnsTlts,12933 +setuptools/_vendor/packaging/utils.py,sha256=VaTC0Ei7zO2xl9ARiWmz2YFLFt89PuuhLbAlXMyAGms,1520 +setuptools/_vendor/packaging/version.py,sha256=Npdwnb8OHedj_2L86yiUqscujb7w_i5gmSK1PhOAFzg,11978 +setuptools/command/__init__.py,sha256=QCAuA9whnq8Bnoc0bBaS6Lw_KAUO0DiHYZQXEMNn5hg,568 +setuptools/command/alias.py,sha256=KjpE0sz_SDIHv3fpZcIQK-sCkJz-SrC6Gmug6b9Nkc8,2426 +setuptools/command/bdist_egg.py,sha256=nnfV8Ah8IRC_Ifv5Loa9FdxL66MVbyDXwy-foP810zM,18185 +setuptools/command/bdist_rpm.py,sha256=B7l0TnzCGb-0nLlm6rS00jWLkojASwVmdhW2w5Qz_Ak,1508 +setuptools/command/bdist_wininst.py,sha256=_6dz3lpB1tY200LxKPLM7qgwTCceOMgaWFF-jW2-pm0,637 +setuptools/command/build_clib.py,sha256=bQ9aBr-5ZSO-9fGsGsDLz0mnnFteHUZnftVLkhvHDq0,4484 +setuptools/command/build_ext.py,sha256=Ib42YUGksBswm2mL5xmQPF6NeTA6HcqrvAtEgFCv32A,13019 +setuptools/command/build_py.py,sha256=yWyYaaS9F3o9JbIczn064A5g1C5_UiKRDxGaTqYbtLE,9596 +setuptools/command/develop.py,sha256=MQlnGS6uP19erK2JCNOyQYoYyquk3PADrqrrinqqLtA,8184 +setuptools/command/dist_info.py,sha256=5t6kOfrdgALT-P3ogss6PF9k-Leyesueycuk3dUyZnI,960 +setuptools/command/easy_install.py,sha256=0lY8Agxe-7IgMtxgxFuOY1NrDlBzOUlpCKsvayXlTYY,89903 +setuptools/command/egg_info.py,sha256=0e_TXrMfpa8nGTO7GmJcmpPCMWzliZi6zt9aMchlumc,25578 +setuptools/command/install.py,sha256=8doMxeQEDoK4Eco0mO2WlXXzzp9QnsGJQ7Z7yWkZPG8,4705 +setuptools/command/install_egg_info.py,sha256=4zq_Ad3jE-EffParuyDEnvxU6efB-Xhrzdr8aB6Ln_8,3195 +setuptools/command/install_lib.py,sha256=9zdc-H5h6RPxjySRhOwi30E_WfcVva7gpfhZ5ata60w,5023 +setuptools/command/install_scripts.py,sha256=UD0rEZ6861mTYhIdzcsqKnUl8PozocXWl9VBQ1VTWnc,2439 +setuptools/command/launcher manifest.xml,sha256=xlLbjWrB01tKC0-hlVkOKkiSPbzMml2eOPtJ_ucCnbE,628 +setuptools/command/py36compat.py,sha256=SzjZcOxF7zdFUT47Zv2n7AM3H8koDys_0OpS-n9gIfc,4986 +setuptools/command/register.py,sha256=kk3DxXCb5lXTvqnhfwx2g6q7iwbUmgTyXUCaBooBOUk,468 +setuptools/command/rotate.py,sha256=co5C1EkI7P0GGT6Tqz-T2SIj2LBJTZXYELpmao6d4KQ,2164 +setuptools/command/saveopts.py,sha256=za7QCBcQimKKriWcoCcbhxPjUz30gSB74zuTL47xpP4,658 +setuptools/command/sdist.py,sha256=IL1LepD2h8qGKOFJ3rrQVbjNH_Q6ViD40l0QADr4MEU,8088 +setuptools/command/setopt.py,sha256=NTWDyx-gjDF-txf4dO577s7LOzHVoKR0Mq33rFxaRr8,5085 +setuptools/command/test.py,sha256=u2kXngIIdSYqtvwFlHiN6Iye1IB4TU6uadB2uiV1szw,9602 +setuptools/command/upload.py,sha256=XT3YFVfYPAmA5qhGg0euluU98ftxRUW-PzKcODMLxUs,462 +setuptools/command/upload_docs.py,sha256=oXiGplM_cUKLwE4CWWw98RzCufAu8tBhMC97GegFcms,7311 +setuptools/extern/__init__.py,sha256=4q9gtShB1XFP6CisltsyPqtcfTO6ZM9Lu1QBl3l-qmo,2514 +setuptools-44.0.0.dist-info/AUTHORS.txt,sha256=RtqU9KfonVGhI48DAA4-yTOBUhBtQTjFhaDzHoyh7uU,21518 +setuptools-44.0.0.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090 +setuptools-44.0.0.dist-info/METADATA,sha256=L93fcafgVw4xoJUNG0lehyy0prVj-jU_JFxRh0ZUtos,3523 +setuptools-44.0.0.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +setuptools-44.0.0.dist-info/dependency_links.txt,sha256=HlkCFkoK5TbZ5EMLbLKYhLcY_E31kBWD8TqW2EgmatQ,239 +setuptools-44.0.0.dist-info/entry_points.txt,sha256=ZmIqlp-SBdsBS2cuetmU2NdSOs4DG0kxctUR9UJ8Xk0,3150 +setuptools-44.0.0.dist-info/top_level.txt,sha256=2HUXVVwA4Pff1xgTFr3GsTXXKaPaO6vlG6oNJ_4u4Tg,38 +setuptools-44.0.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +setuptools-44.0.0.dist-info/RECORD,, +setuptools/_vendor/__init__.cpython-38.pyc,, +setuptools/__init__.cpython-38.pyc,, +../../../bin/easy_install-3.8,, +setuptools/extension.cpython-38.pyc,, +setuptools/glob.cpython-38.pyc,, +setuptools/command/setopt.cpython-38.pyc,, +setuptools/_vendor/packaging/_structures.cpython-38.pyc,, +setuptools/extern/__pycache__,, +setuptools/wheel.cpython-38.pyc,, +setuptools/_vendor/six.cpython-38.pyc,, +setuptools/command/rotate.cpython-38.pyc,, +setuptools/command/test.cpython-38.pyc,, +setuptools/_vendor/packaging/requirements.cpython-38.pyc,, +setuptools/_vendor/packaging/_compat.cpython-38.pyc,, +setuptools/_vendor/packaging/__pycache__,, +setuptools/namespaces.cpython-38.pyc,, +setuptools/dep_util.cpython-38.pyc,, +setuptools/ssl_support.cpython-38.pyc,, +setuptools/command/__init__.cpython-38.pyc,, +setuptools/_vendor/packaging/tags.cpython-38.pyc,, +setuptools/package_index.cpython-38.pyc,, +setuptools-44.0.0.dist-info/INSTALLER,, +easy_install.cpython-38.pyc,, +setuptools/command/install_lib.cpython-38.pyc,, +setuptools/_vendor/__pycache__,, +setuptools/__pycache__,, +setuptools/unicode_utils.cpython-38.pyc,, +setuptools/site-patch.cpython-38.pyc,, +setuptools/command/upload.cpython-38.pyc,, +setuptools/sandbox.cpython-38.pyc,, +setuptools/py33compat.cpython-38.pyc,, +setuptools/command/register.cpython-38.pyc,, +setuptools/command/dist_info.cpython-38.pyc,, +setuptools/command/easy_install.cpython-38.pyc,, +setuptools/installer.cpython-38.pyc,, +setuptools/_vendor/packaging/__about__.cpython-38.pyc,, +setuptools/py34compat.cpython-38.pyc,, +setuptools/command/egg_info.cpython-38.pyc,, +setuptools/_vendor/packaging/utils.cpython-38.pyc,, +setuptools/_vendor/packaging/version.cpython-38.pyc,, +setuptools/dist.cpython-38.pyc,, +../../../bin/easy_install,, +setuptools/_vendor/packaging/markers.cpython-38.pyc,, +setuptools/config.cpython-38.pyc,, +setuptools/command/bdist_rpm.cpython-38.pyc,, +setuptools/command/upload_docs.cpython-38.pyc,, +setuptools/command/__pycache__,, +setuptools/depends.cpython-38.pyc,, +setuptools/command/bdist_egg.cpython-38.pyc,, +setuptools/lib2to3_ex.cpython-38.pyc,, +setuptools/version.cpython-38.pyc,, +setuptools/archive_util.cpython-38.pyc,, +setuptools-44.0.0.virtualenv,, +setuptools/errors.cpython-38.pyc,, +setuptools/_deprecation_warning.cpython-38.pyc,, +setuptools/command/build_ext.cpython-38.pyc,, +setuptools/command/bdist_wininst.cpython-38.pyc,, +../../../bin/easy_install3,, +setuptools/command/install_scripts.cpython-38.pyc,, +setuptools/py27compat.cpython-38.pyc,, +setuptools/command/install_egg_info.cpython-38.pyc,, +setuptools-44.0.0.dist-info/__pycache__,, +setuptools/command/build_clib.cpython-38.pyc,, +setuptools/command/install.cpython-38.pyc,, +setuptools/extern/__init__.cpython-38.pyc,, +setuptools/command/develop.cpython-38.pyc,, +setuptools/msvc.cpython-38.pyc,, +setuptools/_vendor/packaging/__init__.cpython-38.pyc,, +setuptools/build_meta.cpython-38.pyc,, +setuptools/command/sdist.cpython-38.pyc,, +setuptools/launch.cpython-38.pyc,, +setuptools/command/saveopts.cpython-38.pyc,, +setuptools/windows_support.cpython-38.pyc,, +setuptools/_vendor/pyparsing.cpython-38.pyc,, +setuptools/monkey.cpython-38.pyc,, +setuptools/py31compat.cpython-38.pyc,, +setuptools/_vendor/packaging/specifiers.cpython-38.pyc,, +setuptools/command/py36compat.cpython-38.pyc,, +setuptools/_vendor/ordered_set.cpython-38.pyc,, +setuptools/_imp.cpython-38.pyc,, +setuptools/command/build_py.cpython-38.pyc,, +setuptools/command/alias.cpython-38.pyc,, \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/WHEEL b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ef99c6cf3283b50a273ac4c6d009a0aa85597070 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/dependency_links.txt b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/dependency_links.txt new file mode 100644 index 0000000000000000000000000000000000000000..e87d02103ede91545d70783dd59653d183424b68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/dependency_links.txt @@ -0,0 +1,2 @@ +https://files.pythonhosted.org/packages/source/c/certifi/certifi-2016.9.26.tar.gz#md5=baa81e951a29958563689d868ef1064d +https://files.pythonhosted.org/packages/source/w/wincertstore/wincertstore-0.2.zip#md5=ae728f2f007185648d0c7a8679b361e2 diff --git a/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/entry_points.txt b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..0fed3f1d83f3eb690dddad3f050da3d3f021eb6a --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/entry_points.txt @@ -0,0 +1,68 @@ +[console_scripts] +easy_install = setuptools.command.easy_install:main + +[distutils.commands] +alias = setuptools.command.alias:alias +bdist_egg = setuptools.command.bdist_egg:bdist_egg +bdist_rpm = setuptools.command.bdist_rpm:bdist_rpm +bdist_wininst = setuptools.command.bdist_wininst:bdist_wininst +build_clib = setuptools.command.build_clib:build_clib +build_ext = setuptools.command.build_ext:build_ext +build_py = setuptools.command.build_py:build_py +develop = setuptools.command.develop:develop +dist_info = setuptools.command.dist_info:dist_info +easy_install = setuptools.command.easy_install:easy_install +egg_info = setuptools.command.egg_info:egg_info +install = setuptools.command.install:install +install_egg_info = setuptools.command.install_egg_info:install_egg_info +install_lib = setuptools.command.install_lib:install_lib +install_scripts = setuptools.command.install_scripts:install_scripts +rotate = setuptools.command.rotate:rotate +saveopts = setuptools.command.saveopts:saveopts +sdist = setuptools.command.sdist:sdist +setopt = setuptools.command.setopt:setopt +test = setuptools.command.test:test +upload_docs = setuptools.command.upload_docs:upload_docs + +[distutils.setup_keywords] +convert_2to3_doctests = setuptools.dist:assert_string_list +dependency_links = setuptools.dist:assert_string_list +eager_resources = setuptools.dist:assert_string_list +entry_points = setuptools.dist:check_entry_points +exclude_package_data = setuptools.dist:check_package_data +extras_require = setuptools.dist:check_extras +include_package_data = setuptools.dist:assert_bool +install_requires = setuptools.dist:check_requirements +namespace_packages = setuptools.dist:check_nsp +package_data = setuptools.dist:check_package_data +packages = setuptools.dist:check_packages +python_requires = setuptools.dist:check_specifier +setup_requires = setuptools.dist:check_requirements +test_loader = setuptools.dist:check_importable +test_runner = setuptools.dist:check_importable +test_suite = setuptools.dist:check_test_suite +tests_require = setuptools.dist:check_requirements +use_2to3 = setuptools.dist:assert_bool +use_2to3_exclude_fixers = setuptools.dist:assert_string_list +use_2to3_fixers = setuptools.dist:assert_string_list +zip_safe = setuptools.dist:assert_bool + +[egg_info.writers] +PKG-INFO = setuptools.command.egg_info:write_pkg_info +dependency_links.txt = setuptools.command.egg_info:overwrite_arg +depends.txt = setuptools.command.egg_info:warn_depends_obsolete +eager_resources.txt = setuptools.command.egg_info:overwrite_arg +entry_points.txt = setuptools.command.egg_info:write_entries +namespace_packages.txt = setuptools.command.egg_info:overwrite_arg +requires.txt = setuptools.command.egg_info:write_requirements +top_level.txt = setuptools.command.egg_info:write_toplevel_names + +[setuptools.finalize_distribution_options] +2to3_doctests = setuptools.dist:Distribution._finalize_2to3_doctests +features = setuptools.dist:Distribution._finalize_feature_opts +keywords = setuptools.dist:Distribution._finalize_setup_keywords +parent_finalize = setuptools.dist:_Distribution.finalize_options + +[setuptools.installation] +eggsecutable = setuptools.command.easy_install:bootstrap + diff --git a/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..4577c6a795e510bf7578236665f582c3770fb42e --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/top_level.txt @@ -0,0 +1,3 @@ +easy_install +pkg_resources +setuptools diff --git a/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/zip-safe b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/zip-safe new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools-44.0.0.dist-info/zip-safe @@ -0,0 +1 @@ + diff --git a/robot/lib/python3.8/site-packages/setuptools-44.0.0.virtualenv b/robot/lib/python3.8/site-packages/setuptools-44.0.0.virtualenv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/setuptools/__init__.py b/robot/lib/python3.8/site-packages/setuptools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a71b2bbdc6170963a66959c48080c1dedc7bb703 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/__init__.py @@ -0,0 +1,228 @@ +"""Extensions to the 'distutils' for large or complex distributions""" + +import os +import sys +import functools +import distutils.core +import distutils.filelist +import re +from distutils.errors import DistutilsOptionError +from distutils.util import convert_path +from fnmatch import fnmatchcase + +from ._deprecation_warning import SetuptoolsDeprecationWarning + +from setuptools.extern.six import PY3, string_types +from setuptools.extern.six.moves import filter, map + +import setuptools.version +from setuptools.extension import Extension +from setuptools.dist import Distribution, Feature +from setuptools.depends import Require +from . import monkey + +__metaclass__ = type + + +__all__ = [ + 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', + 'SetuptoolsDeprecationWarning', + 'find_packages' +] + +if PY3: + __all__.append('find_namespace_packages') + +__version__ = setuptools.version.__version__ + +bootstrap_install_from = None + +# If we run 2to3 on .py files, should we also convert docstrings? +# Default: yes; assume that we can detect doctests reliably +run_2to3_on_doctests = True +# Standard package names for fixer packages +lib2to3_fixer_packages = ['lib2to3.fixes'] + + +class PackageFinder: + """ + Generate a list of all Python packages found within a directory + """ + + @classmethod + def find(cls, where='.', exclude=(), include=('*',)): + """Return a list all Python packages found within directory 'where' + + 'where' is the root directory which will be searched for packages. It + should be supplied as a "cross-platform" (i.e. URL-style) path; it will + be converted to the appropriate local path syntax. + + 'exclude' is a sequence of package names to exclude; '*' can be used + as a wildcard in the names, such that 'foo.*' will exclude all + subpackages of 'foo' (but not 'foo' itself). + + 'include' is a sequence of package names to include. If it's + specified, only the named packages will be included. If it's not + specified, all found packages will be included. 'include' can contain + shell style wildcard patterns just like 'exclude'. + """ + + return list(cls._find_packages_iter( + convert_path(where), + cls._build_filter('ez_setup', '*__pycache__', *exclude), + cls._build_filter(*include))) + + @classmethod + def _find_packages_iter(cls, where, exclude, include): + """ + All the packages found in 'where' that pass the 'include' filter, but + not the 'exclude' filter. + """ + for root, dirs, files in os.walk(where, followlinks=True): + # Copy dirs to iterate over it, then empty dirs. + all_dirs = dirs[:] + dirs[:] = [] + + for dir in all_dirs: + full_path = os.path.join(root, dir) + rel_path = os.path.relpath(full_path, where) + package = rel_path.replace(os.path.sep, '.') + + # Skip directory trees that are not valid packages + if ('.' in dir or not cls._looks_like_package(full_path)): + continue + + # Should this package be included? + if include(package) and not exclude(package): + yield package + + # Keep searching subdirectories, as there may be more packages + # down there, even if the parent was excluded. + dirs.append(dir) + + @staticmethod + def _looks_like_package(path): + """Does a directory look like a package?""" + return os.path.isfile(os.path.join(path, '__init__.py')) + + @staticmethod + def _build_filter(*patterns): + """ + Given a list of patterns, return a callable that will be true only if + the input matches at least one of the patterns. + """ + return lambda name: any(fnmatchcase(name, pat=pat) for pat in patterns) + + +class PEP420PackageFinder(PackageFinder): + @staticmethod + def _looks_like_package(path): + return True + + +find_packages = PackageFinder.find + +if PY3: + find_namespace_packages = PEP420PackageFinder.find + + +def _install_setup_requires(attrs): + # Note: do not use `setuptools.Distribution` directly, as + # our PEP 517 backend patch `distutils.core.Distribution`. + dist = distutils.core.Distribution(dict( + (k, v) for k, v in attrs.items() + if k in ('dependency_links', 'setup_requires') + )) + # Honor setup.cfg's options. + dist.parse_config_files(ignore_option_errors=True) + if dist.setup_requires: + dist.fetch_build_eggs(dist.setup_requires) + + +def setup(**attrs): + # Make sure we have any requirements needed to interpret 'attrs'. + _install_setup_requires(attrs) + return distutils.core.setup(**attrs) + +setup.__doc__ = distutils.core.setup.__doc__ + + +_Command = monkey.get_unpatched(distutils.core.Command) + + +class Command(_Command): + __doc__ = _Command.__doc__ + + command_consumes_arguments = False + + def __init__(self, dist, **kw): + """ + Construct the command for dist, updating + vars(self) with any keyword parameters. + """ + _Command.__init__(self, dist) + vars(self).update(kw) + + def _ensure_stringlike(self, option, what, default=None): + val = getattr(self, option) + if val is None: + setattr(self, option, default) + return default + elif not isinstance(val, string_types): + raise DistutilsOptionError("'%s' must be a %s (got `%s`)" + % (option, what, val)) + return val + + def ensure_string_list(self, option): + r"""Ensure that 'option' is a list of strings. If 'option' is + currently a string, we split it either on /,\s*/ or /\s+/, so + "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become + ["foo", "bar", "baz"]. + """ + val = getattr(self, option) + if val is None: + return + elif isinstance(val, string_types): + setattr(self, option, re.split(r',\s*|\s+', val)) + else: + if isinstance(val, list): + ok = all(isinstance(v, string_types) for v in val) + else: + ok = False + if not ok: + raise DistutilsOptionError( + "'%s' must be a list of strings (got %r)" + % (option, val)) + + def reinitialize_command(self, command, reinit_subcommands=0, **kw): + cmd = _Command.reinitialize_command(self, command, reinit_subcommands) + vars(cmd).update(kw) + return cmd + + +def _find_all_simple(path): + """ + Find all files under 'path' + """ + results = ( + os.path.join(base, file) + for base, dirs, files in os.walk(path, followlinks=True) + for file in files + ) + return filter(os.path.isfile, results) + + +def findall(dir=os.curdir): + """ + Find all files under 'dir' and return the list of full filenames. + Unless dir is '.', return full filenames with dir prepended. + """ + files = _find_all_simple(dir) + if dir == os.curdir: + make_rel = functools.partial(os.path.relpath, start=dir) + files = map(make_rel, files) + return list(files) + + +# Apply monkey patches +monkey.patch_all() diff --git a/robot/lib/python3.8/site-packages/setuptools/_deprecation_warning.py b/robot/lib/python3.8/site-packages/setuptools/_deprecation_warning.py new file mode 100644 index 0000000000000000000000000000000000000000..086b64dd3817c0c1a194ffc1959eeffdd2695bef --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_deprecation_warning.py @@ -0,0 +1,7 @@ +class SetuptoolsDeprecationWarning(Warning): + """ + Base class for warning deprecations in ``setuptools`` + + This class is not derived from ``DeprecationWarning``, and as such is + visible by default. + """ diff --git a/robot/lib/python3.8/site-packages/setuptools/_imp.py b/robot/lib/python3.8/site-packages/setuptools/_imp.py new file mode 100644 index 0000000000000000000000000000000000000000..a3cce9b284b1e580c1715c5e300a18077d63e8ce --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_imp.py @@ -0,0 +1,73 @@ +""" +Re-implementation of find_module and get_frozen_object +from the deprecated imp module. +""" + +import os +import importlib.util +import importlib.machinery + +from .py34compat import module_from_spec + + +PY_SOURCE = 1 +PY_COMPILED = 2 +C_EXTENSION = 3 +C_BUILTIN = 6 +PY_FROZEN = 7 + + +def find_module(module, paths=None): + """Just like 'imp.find_module()', but with package support""" + spec = importlib.util.find_spec(module, paths) + if spec is None: + raise ImportError("Can't find %s" % module) + if not spec.has_location and hasattr(spec, 'submodule_search_locations'): + spec = importlib.util.spec_from_loader('__init__.py', spec.loader) + + kind = -1 + file = None + static = isinstance(spec.loader, type) + if spec.origin == 'frozen' or static and issubclass( + spec.loader, importlib.machinery.FrozenImporter): + kind = PY_FROZEN + path = None # imp compabilty + suffix = mode = '' # imp compability + elif spec.origin == 'built-in' or static and issubclass( + spec.loader, importlib.machinery.BuiltinImporter): + kind = C_BUILTIN + path = None # imp compabilty + suffix = mode = '' # imp compability + elif spec.has_location: + path = spec.origin + suffix = os.path.splitext(path)[1] + mode = 'r' if suffix in importlib.machinery.SOURCE_SUFFIXES else 'rb' + + if suffix in importlib.machinery.SOURCE_SUFFIXES: + kind = PY_SOURCE + elif suffix in importlib.machinery.BYTECODE_SUFFIXES: + kind = PY_COMPILED + elif suffix in importlib.machinery.EXTENSION_SUFFIXES: + kind = C_EXTENSION + + if kind in {PY_SOURCE, PY_COMPILED}: + file = open(path, mode) + else: + path = None + suffix = mode = '' + + return file, path, (suffix, mode, kind) + + +def get_frozen_object(module, paths=None): + spec = importlib.util.find_spec(module, paths) + if not spec: + raise ImportError("Can't find %s" % module) + return spec.loader.get_code(module) + + +def get_module(module, paths, info): + spec = importlib.util.find_spec(module, paths) + if not spec: + raise ImportError("Can't find %s" % module) + return module_from_spec(spec) diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/__init__.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/ordered_set.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/ordered_set.py new file mode 100644 index 0000000000000000000000000000000000000000..14876000de895a609d5b9f3de39c3c8fc44ef1fc --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/ordered_set.py @@ -0,0 +1,488 @@ +""" +An OrderedSet is a custom MutableSet that remembers its order, so that every +entry has an index that can be looked up. + +Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger, +and released under the MIT license. +""" +import itertools as it +from collections import deque + +try: + # Python 3 + from collections.abc import MutableSet, Sequence +except ImportError: + # Python 2.7 + from collections import MutableSet, Sequence + +SLICE_ALL = slice(None) +__version__ = "3.1" + + +def is_iterable(obj): + """ + Are we being asked to look up a list of things, instead of a single thing? + We check for the `__iter__` attribute so that this can cover types that + don't have to be known by this module, such as NumPy arrays. + + Strings, however, should be considered as atomic values to look up, not + iterables. The same goes for tuples, since they are immutable and therefore + valid entries. + + We don't need to check for the Python 2 `unicode` type, because it doesn't + have an `__iter__` attribute anyway. + """ + return ( + hasattr(obj, "__iter__") + and not isinstance(obj, str) + and not isinstance(obj, tuple) + ) + + +class OrderedSet(MutableSet, Sequence): + """ + An OrderedSet is a custom MutableSet that remembers its order, so that + every entry has an index that can be looked up. + + Example: + >>> OrderedSet([1, 1, 2, 3, 2]) + OrderedSet([1, 2, 3]) + """ + + def __init__(self, iterable=None): + self.items = [] + self.map = {} + if iterable is not None: + self |= iterable + + def __len__(self): + """ + Returns the number of unique elements in the ordered set + + Example: + >>> len(OrderedSet([])) + 0 + >>> len(OrderedSet([1, 2])) + 2 + """ + return len(self.items) + + def __getitem__(self, index): + """ + Get the item at a given index. + + If `index` is a slice, you will get back that slice of items, as a + new OrderedSet. + + If `index` is a list or a similar iterable, you'll get a list of + items corresponding to those indices. This is similar to NumPy's + "fancy indexing". The result is not an OrderedSet because you may ask + for duplicate indices, and the number of elements returned should be + the number of elements asked for. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset[1] + 2 + """ + if isinstance(index, slice) and index == SLICE_ALL: + return self.copy() + elif is_iterable(index): + return [self.items[i] for i in index] + elif hasattr(index, "__index__") or isinstance(index, slice): + result = self.items[index] + if isinstance(result, list): + return self.__class__(result) + else: + return result + else: + raise TypeError("Don't know how to index an OrderedSet by %r" % index) + + def copy(self): + """ + Return a shallow copy of this object. + + Example: + >>> this = OrderedSet([1, 2, 3]) + >>> other = this.copy() + >>> this == other + True + >>> this is other + False + """ + return self.__class__(self) + + def __getstate__(self): + if len(self) == 0: + # The state can't be an empty list. + # We need to return a truthy value, or else __setstate__ won't be run. + # + # This could have been done more gracefully by always putting the state + # in a tuple, but this way is backwards- and forwards- compatible with + # previous versions of OrderedSet. + return (None,) + else: + return list(self) + + def __setstate__(self, state): + if state == (None,): + self.__init__([]) + else: + self.__init__(state) + + def __contains__(self, key): + """ + Test if the item is in this ordered set + + Example: + >>> 1 in OrderedSet([1, 3, 2]) + True + >>> 5 in OrderedSet([1, 3, 2]) + False + """ + return key in self.map + + def add(self, key): + """ + Add `key` as an item to this OrderedSet, then return its index. + + If `key` is already in the OrderedSet, return the index it already + had. + + Example: + >>> oset = OrderedSet() + >>> oset.append(3) + 0 + >>> print(oset) + OrderedSet([3]) + """ + if key not in self.map: + self.map[key] = len(self.items) + self.items.append(key) + return self.map[key] + + append = add + + def update(self, sequence): + """ + Update the set with the given iterable sequence, then return the index + of the last element inserted. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.update([3, 1, 5, 1, 4]) + 4 + >>> print(oset) + OrderedSet([1, 2, 3, 5, 4]) + """ + item_index = None + try: + for item in sequence: + item_index = self.add(item) + except TypeError: + raise ValueError( + "Argument needs to be an iterable, got %s" % type(sequence) + ) + return item_index + + def index(self, key): + """ + Get the index of a given entry, raising an IndexError if it's not + present. + + `key` can be an iterable of entries that is not a string, in which case + this returns a list of indices. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.index(2) + 1 + """ + if is_iterable(key): + return [self.index(subkey) for subkey in key] + return self.map[key] + + # Provide some compatibility with pd.Index + get_loc = index + get_indexer = index + + def pop(self): + """ + Remove and return the last element from the set. + + Raises KeyError if the set is empty. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.pop() + 3 + """ + if not self.items: + raise KeyError("Set is empty") + + elem = self.items[-1] + del self.items[-1] + del self.map[elem] + return elem + + def discard(self, key): + """ + Remove an element. Do not raise an exception if absent. + + The MutableSet mixin uses this to implement the .remove() method, which + *does* raise an error when asked to remove a non-existent item. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.discard(2) + >>> print(oset) + OrderedSet([1, 3]) + >>> oset.discard(2) + >>> print(oset) + OrderedSet([1, 3]) + """ + if key in self: + i = self.map[key] + del self.items[i] + del self.map[key] + for k, v in self.map.items(): + if v >= i: + self.map[k] = v - 1 + + def clear(self): + """ + Remove all items from this OrderedSet. + """ + del self.items[:] + self.map.clear() + + def __iter__(self): + """ + Example: + >>> list(iter(OrderedSet([1, 2, 3]))) + [1, 2, 3] + """ + return iter(self.items) + + def __reversed__(self): + """ + Example: + >>> list(reversed(OrderedSet([1, 2, 3]))) + [3, 2, 1] + """ + return reversed(self.items) + + def __repr__(self): + if not self: + return "%s()" % (self.__class__.__name__,) + return "%s(%r)" % (self.__class__.__name__, list(self)) + + def __eq__(self, other): + """ + Returns true if the containers have the same items. If `other` is a + Sequence, then order is checked, otherwise it is ignored. + + Example: + >>> oset = OrderedSet([1, 3, 2]) + >>> oset == [1, 3, 2] + True + >>> oset == [1, 2, 3] + False + >>> oset == [2, 3] + False + >>> oset == OrderedSet([3, 2, 1]) + False + """ + # In Python 2 deque is not a Sequence, so treat it as one for + # consistent behavior with Python 3. + if isinstance(other, (Sequence, deque)): + # Check that this OrderedSet contains the same elements, in the + # same order, as the other object. + return list(self) == list(other) + try: + other_as_set = set(other) + except TypeError: + # If `other` can't be converted into a set, it's not equal. + return False + else: + return set(self) == other_as_set + + def union(self, *sets): + """ + Combines all unique items. + Each items order is defined by its first appearance. + + Example: + >>> oset = OrderedSet.union(OrderedSet([3, 1, 4, 1, 5]), [1, 3], [2, 0]) + >>> print(oset) + OrderedSet([3, 1, 4, 5, 2, 0]) + >>> oset.union([8, 9]) + OrderedSet([3, 1, 4, 5, 2, 0, 8, 9]) + >>> oset | {10} + OrderedSet([3, 1, 4, 5, 2, 0, 10]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + containers = map(list, it.chain([self], sets)) + items = it.chain.from_iterable(containers) + return cls(items) + + def __and__(self, other): + # the parent implementation of this is backwards + return self.intersection(other) + + def intersection(self, *sets): + """ + Returns elements in common between all sets. Order is defined only + by the first set. + + Example: + >>> oset = OrderedSet.intersection(OrderedSet([0, 1, 2, 3]), [1, 2, 3]) + >>> print(oset) + OrderedSet([1, 2, 3]) + >>> oset.intersection([2, 4, 5], [1, 2, 3, 4]) + OrderedSet([2]) + >>> oset.intersection() + OrderedSet([1, 2, 3]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + if sets: + common = set.intersection(*map(set, sets)) + items = (item for item in self if item in common) + else: + items = self + return cls(items) + + def difference(self, *sets): + """ + Returns all elements that are in this set but not the others. + + Example: + >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2])) + OrderedSet([1, 3]) + >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]), OrderedSet([3])) + OrderedSet([1]) + >>> OrderedSet([1, 2, 3]) - OrderedSet([2]) + OrderedSet([1, 3]) + >>> OrderedSet([1, 2, 3]).difference() + OrderedSet([1, 2, 3]) + """ + cls = self.__class__ + if sets: + other = set.union(*map(set, sets)) + items = (item for item in self if item not in other) + else: + items = self + return cls(items) + + def issubset(self, other): + """ + Report whether another set contains this set. + + Example: + >>> OrderedSet([1, 2, 3]).issubset({1, 2}) + False + >>> OrderedSet([1, 2, 3]).issubset({1, 2, 3, 4}) + True + >>> OrderedSet([1, 2, 3]).issubset({1, 4, 3, 5}) + False + """ + if len(self) > len(other): # Fast check for obvious cases + return False + return all(item in other for item in self) + + def issuperset(self, other): + """ + Report whether this set contains another set. + + Example: + >>> OrderedSet([1, 2]).issuperset([1, 2, 3]) + False + >>> OrderedSet([1, 2, 3, 4]).issuperset({1, 2, 3}) + True + >>> OrderedSet([1, 4, 3, 5]).issuperset({1, 2, 3}) + False + """ + if len(self) < len(other): # Fast check for obvious cases + return False + return all(item in self for item in other) + + def symmetric_difference(self, other): + """ + Return the symmetric difference of two OrderedSets as a new set. + That is, the new set will contain all elements that are in exactly + one of the sets. + + Their order will be preserved, with elements from `self` preceding + elements from `other`. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.symmetric_difference(other) + OrderedSet([4, 5, 9, 2]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + diff1 = cls(self).difference(other) + diff2 = cls(other).difference(self) + return diff1.union(diff2) + + def _update_items(self, items): + """ + Replace the 'items' list of this OrderedSet with a new one, updating + self.map accordingly. + """ + self.items = items + self.map = {item: idx for (idx, item) in enumerate(items)} + + def difference_update(self, *sets): + """ + Update this OrderedSet to remove items from one or more other sets. + + Example: + >>> this = OrderedSet([1, 2, 3]) + >>> this.difference_update(OrderedSet([2, 4])) + >>> print(this) + OrderedSet([1, 3]) + + >>> this = OrderedSet([1, 2, 3, 4, 5]) + >>> this.difference_update(OrderedSet([2, 4]), OrderedSet([1, 4, 6])) + >>> print(this) + OrderedSet([3, 5]) + """ + items_to_remove = set() + for other in sets: + items_to_remove |= set(other) + self._update_items([item for item in self.items if item not in items_to_remove]) + + def intersection_update(self, other): + """ + Update this OrderedSet to keep only items in another set, preserving + their order in this set. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.intersection_update(other) + >>> print(this) + OrderedSet([1, 3, 7]) + """ + other = set(other) + self._update_items([item for item in self.items if item in other]) + + def symmetric_difference_update(self, other): + """ + Update this OrderedSet to remove items from another set, then + add items from the other set that were not present in this set. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.symmetric_difference_update(other) + >>> print(this) + OrderedSet([4, 5, 9, 2]) + """ + items_to_add = [item for item in other if item not in self] + items_to_remove = set(other) + self._update_items( + [item for item in self.items if item not in items_to_remove] + items_to_add + ) diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/__about__.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/__about__.py new file mode 100644 index 0000000000000000000000000000000000000000..dc95138d049ba3194964d528b552a6d1514fa382 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/__about__.py @@ -0,0 +1,27 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +__all__ = [ + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", +] + +__title__ = "packaging" +__summary__ = "Core utilities for Python packages" +__uri__ = "https://github.com/pypa/packaging" + +__version__ = "19.2" + +__author__ = "Donald Stufft and individual contributors" +__email__ = "donald@stufft.io" + +__license__ = "BSD or Apache License, Version 2.0" +__copyright__ = "Copyright 2014-2019 %s" % __author__ diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/__init__.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a0cf67df5245be16a020ca048832e180f7ce8661 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/__init__.py @@ -0,0 +1,26 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +from .__about__ import ( + __author__, + __copyright__, + __email__, + __license__, + __summary__, + __title__, + __uri__, + __version__, +) + +__all__ = [ + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", +] diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/_compat.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/_compat.py new file mode 100644 index 0000000000000000000000000000000000000000..25da473c196855ad59a6d2d785ef1ddef49795be --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/_compat.py @@ -0,0 +1,31 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import sys + + +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + +# flake8: noqa + +if PY3: + string_types = (str,) +else: + string_types = (basestring,) + + +def with_metaclass(meta, *bases): + """ + Create a base class with a metaclass. + """ + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + + return type.__new__(metaclass, "temporary_class", (), {}) diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/_structures.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/_structures.py new file mode 100644 index 0000000000000000000000000000000000000000..68dcca634d8e3f0081bad2f9ae5e653a2942db68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/_structures.py @@ -0,0 +1,68 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + + +class Infinity(object): + def __repr__(self): + return "Infinity" + + def __hash__(self): + return hash(repr(self)) + + def __lt__(self, other): + return False + + def __le__(self, other): + return False + + def __eq__(self, other): + return isinstance(other, self.__class__) + + def __ne__(self, other): + return not isinstance(other, self.__class__) + + def __gt__(self, other): + return True + + def __ge__(self, other): + return True + + def __neg__(self): + return NegativeInfinity + + +Infinity = Infinity() + + +class NegativeInfinity(object): + def __repr__(self): + return "-Infinity" + + def __hash__(self): + return hash(repr(self)) + + def __lt__(self, other): + return True + + def __le__(self, other): + return True + + def __eq__(self, other): + return isinstance(other, self.__class__) + + def __ne__(self, other): + return not isinstance(other, self.__class__) + + def __gt__(self, other): + return False + + def __ge__(self, other): + return False + + def __neg__(self): + return Infinity + + +NegativeInfinity = NegativeInfinity() diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/markers.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/markers.py new file mode 100644 index 0000000000000000000000000000000000000000..4bdfdb24f2096eac046bb9a576065bb96cfd476e --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/markers.py @@ -0,0 +1,296 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import operator +import os +import platform +import sys + +from setuptools.extern.pyparsing import ParseException, ParseResults, stringStart, stringEnd +from setuptools.extern.pyparsing import ZeroOrMore, Group, Forward, QuotedString +from setuptools.extern.pyparsing import Literal as L # noqa + +from ._compat import string_types +from .specifiers import Specifier, InvalidSpecifier + + +__all__ = [ + "InvalidMarker", + "UndefinedComparison", + "UndefinedEnvironmentName", + "Marker", + "default_environment", +] + + +class InvalidMarker(ValueError): + """ + An invalid marker was found, users should refer to PEP 508. + """ + + +class UndefinedComparison(ValueError): + """ + An invalid operation was attempted on a value that doesn't support it. + """ + + +class UndefinedEnvironmentName(ValueError): + """ + A name was attempted to be used that does not exist inside of the + environment. + """ + + +class Node(object): + def __init__(self, value): + self.value = value + + def __str__(self): + return str(self.value) + + def __repr__(self): + return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) + + def serialize(self): + raise NotImplementedError + + +class Variable(Node): + def serialize(self): + return str(self) + + +class Value(Node): + def serialize(self): + return '"{0}"'.format(self) + + +class Op(Node): + def serialize(self): + return str(self) + + +VARIABLE = ( + L("implementation_version") + | L("platform_python_implementation") + | L("implementation_name") + | L("python_full_version") + | L("platform_release") + | L("platform_version") + | L("platform_machine") + | L("platform_system") + | L("python_version") + | L("sys_platform") + | L("os_name") + | L("os.name") + | L("sys.platform") # PEP-345 + | L("platform.version") # PEP-345 + | L("platform.machine") # PEP-345 + | L("platform.python_implementation") # PEP-345 + | L("python_implementation") # PEP-345 + | L("extra") # undocumented setuptools legacy +) +ALIASES = { + "os.name": "os_name", + "sys.platform": "sys_platform", + "platform.version": "platform_version", + "platform.machine": "platform_machine", + "platform.python_implementation": "platform_python_implementation", + "python_implementation": "platform_python_implementation", +} +VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) + +VERSION_CMP = ( + L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<") +) + +MARKER_OP = VERSION_CMP | L("not in") | L("in") +MARKER_OP.setParseAction(lambda s, l, t: Op(t[0])) + +MARKER_VALUE = QuotedString("'") | QuotedString('"') +MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0])) + +BOOLOP = L("and") | L("or") + +MARKER_VAR = VARIABLE | MARKER_VALUE + +MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR) +MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0])) + +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() + +MARKER_EXPR = Forward() +MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN) +MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) + +MARKER = stringStart + MARKER_EXPR + stringEnd + + +def _coerce_parse_result(results): + if isinstance(results, ParseResults): + return [_coerce_parse_result(i) for i in results] + else: + return results + + +def _format_marker(marker, first=True): + assert isinstance(marker, (list, tuple, string_types)) + + # Sometimes we have a structure like [[...]] which is a single item list + # where the single item is itself it's own list. In that case we want skip + # the rest of this function so that we don't get extraneous () on the + # outside. + if ( + isinstance(marker, list) + and len(marker) == 1 + and isinstance(marker[0], (list, tuple)) + ): + return _format_marker(marker[0]) + + if isinstance(marker, list): + inner = (_format_marker(m, first=False) for m in marker) + if first: + return " ".join(inner) + else: + return "(" + " ".join(inner) + ")" + elif isinstance(marker, tuple): + return " ".join([m.serialize() for m in marker]) + else: + return marker + + +_operators = { + "in": lambda lhs, rhs: lhs in rhs, + "not in": lambda lhs, rhs: lhs not in rhs, + "<": operator.lt, + "<=": operator.le, + "==": operator.eq, + "!=": operator.ne, + ">=": operator.ge, + ">": operator.gt, +} + + +def _eval_op(lhs, op, rhs): + try: + spec = Specifier("".join([op.serialize(), rhs])) + except InvalidSpecifier: + pass + else: + return spec.contains(lhs) + + oper = _operators.get(op.serialize()) + if oper is None: + raise UndefinedComparison( + "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) + ) + + return oper(lhs, rhs) + + +_undefined = object() + + +def _get_env(environment, name): + value = environment.get(name, _undefined) + + if value is _undefined: + raise UndefinedEnvironmentName( + "{0!r} does not exist in evaluation environment.".format(name) + ) + + return value + + +def _evaluate_markers(markers, environment): + groups = [[]] + + for marker in markers: + assert isinstance(marker, (list, tuple, string_types)) + + if isinstance(marker, list): + groups[-1].append(_evaluate_markers(marker, environment)) + elif isinstance(marker, tuple): + lhs, op, rhs = marker + + if isinstance(lhs, Variable): + lhs_value = _get_env(environment, lhs.value) + rhs_value = rhs.value + else: + lhs_value = lhs.value + rhs_value = _get_env(environment, rhs.value) + + groups[-1].append(_eval_op(lhs_value, op, rhs_value)) + else: + assert marker in ["and", "or"] + if marker == "or": + groups.append([]) + + return any(all(item) for item in groups) + + +def format_full_version(info): + version = "{0.major}.{0.minor}.{0.micro}".format(info) + kind = info.releaselevel + if kind != "final": + version += kind[0] + str(info.serial) + return version + + +def default_environment(): + if hasattr(sys, "implementation"): + iver = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name + else: + iver = "0" + implementation_name = "" + + return { + "implementation_name": implementation_name, + "implementation_version": iver, + "os_name": os.name, + "platform_machine": platform.machine(), + "platform_release": platform.release(), + "platform_system": platform.system(), + "platform_version": platform.version(), + "python_full_version": platform.python_version(), + "platform_python_implementation": platform.python_implementation(), + "python_version": ".".join(platform.python_version_tuple()[:2]), + "sys_platform": sys.platform, + } + + +class Marker(object): + def __init__(self, marker): + try: + self._markers = _coerce_parse_result(MARKER.parseString(marker)) + except ParseException as e: + err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( + marker, marker[e.loc : e.loc + 8] + ) + raise InvalidMarker(err_str) + + def __str__(self): + return _format_marker(self._markers) + + def __repr__(self): + return "".format(str(self)) + + def evaluate(self, environment=None): + """Evaluate a marker. + + Return the boolean from evaluating the given marker against the + environment. environment is an optional argument to override all or + part of the determined environment. + + The environment is determined from the current Python process. + """ + current_environment = default_environment() + if environment is not None: + current_environment.update(environment) + + return _evaluate_markers(self._markers, current_environment) diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/requirements.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/requirements.py new file mode 100644 index 0000000000000000000000000000000000000000..8a0c2cb9be06e633b26c7205d6efe42827835910 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/requirements.py @@ -0,0 +1,138 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import string +import re + +from setuptools.extern.pyparsing import stringStart, stringEnd, originalTextFor, ParseException +from setuptools.extern.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine +from setuptools.extern.pyparsing import Literal as L # noqa +from setuptools.extern.six.moves.urllib import parse as urlparse + +from .markers import MARKER_EXPR, Marker +from .specifiers import LegacySpecifier, Specifier, SpecifierSet + + +class InvalidRequirement(ValueError): + """ + An invalid requirement was found, users should refer to PEP 508. + """ + + +ALPHANUM = Word(string.ascii_letters + string.digits) + +LBRACKET = L("[").suppress() +RBRACKET = L("]").suppress() +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() +COMMA = L(",").suppress() +SEMICOLON = L(";").suppress() +AT = L("@").suppress() + +PUNCTUATION = Word("-_.") +IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) +IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) + +NAME = IDENTIFIER("name") +EXTRA = IDENTIFIER + +URI = Regex(r"[^ ]+")("url") +URL = AT + URI + +EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) +EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") + +VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) +VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) + +VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY +VERSION_MANY = Combine( + VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False +)("_raw_spec") +_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) +_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") + +VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") +VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) + +MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") +MARKER_EXPR.setParseAction( + lambda s, l, t: Marker(s[t._original_start : t._original_end]) +) +MARKER_SEPARATOR = SEMICOLON +MARKER = MARKER_SEPARATOR + MARKER_EXPR + +VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) +URL_AND_MARKER = URL + Optional(MARKER) + +NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) + +REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd +# setuptools.extern.pyparsing isn't thread safe during initialization, so we do it eagerly, see +# issue #104 +REQUIREMENT.parseString("x[]") + + +class Requirement(object): + """Parse a requirement. + + Parse a given requirement string into its parts, such as name, specifier, + URL, and extras. Raises InvalidRequirement on a badly-formed requirement + string. + """ + + # TODO: Can we test whether something is contained within a requirement? + # If so how do we do that? Do we need to test against the _name_ of + # the thing as well as the version? What about the markers? + # TODO: Can we normalize the name and extra name? + + def __init__(self, requirement_string): + try: + req = REQUIREMENT.parseString(requirement_string) + except ParseException as e: + raise InvalidRequirement( + 'Parse error at "{0!r}": {1}'.format( + requirement_string[e.loc : e.loc + 8], e.msg + ) + ) + + self.name = req.name + if req.url: + parsed_url = urlparse.urlparse(req.url) + if parsed_url.scheme == "file": + if urlparse.urlunparse(parsed_url) != req.url: + raise InvalidRequirement("Invalid URL given") + elif not (parsed_url.scheme and parsed_url.netloc) or ( + not parsed_url.scheme and not parsed_url.netloc + ): + raise InvalidRequirement("Invalid URL: {0}".format(req.url)) + self.url = req.url + else: + self.url = None + self.extras = set(req.extras.asList() if req.extras else []) + self.specifier = SpecifierSet(req.specifier) + self.marker = req.marker if req.marker else None + + def __str__(self): + parts = [self.name] + + if self.extras: + parts.append("[{0}]".format(",".join(sorted(self.extras)))) + + if self.specifier: + parts.append(str(self.specifier)) + + if self.url: + parts.append("@ {0}".format(self.url)) + if self.marker: + parts.append(" ") + + if self.marker: + parts.append("; {0}".format(self.marker)) + + return "".join(parts) + + def __repr__(self): + return "".format(str(self)) diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/specifiers.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/specifiers.py new file mode 100644 index 0000000000000000000000000000000000000000..743576a080a0af8d0995f307ea6afc645b13ca61 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/specifiers.py @@ -0,0 +1,749 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import abc +import functools +import itertools +import re + +from ._compat import string_types, with_metaclass +from .version import Version, LegacyVersion, parse + + +class InvalidSpecifier(ValueError): + """ + An invalid specifier was found, users should refer to PEP 440. + """ + + +class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): + @abc.abstractmethod + def __str__(self): + """ + Returns the str representation of this Specifier like object. This + should be representative of the Specifier itself. + """ + + @abc.abstractmethod + def __hash__(self): + """ + Returns a hash value for this Specifier like object. + """ + + @abc.abstractmethod + def __eq__(self, other): + """ + Returns a boolean representing whether or not the two Specifier like + objects are equal. + """ + + @abc.abstractmethod + def __ne__(self, other): + """ + Returns a boolean representing whether or not the two Specifier like + objects are not equal. + """ + + @abc.abstractproperty + def prereleases(self): + """ + Returns whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @prereleases.setter + def prereleases(self, value): + """ + Sets whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @abc.abstractmethod + def contains(self, item, prereleases=None): + """ + Determines if the given item is contained within this specifier. + """ + + @abc.abstractmethod + def filter(self, iterable, prereleases=None): + """ + Takes an iterable of items and filters them so that only items which + are contained within this specifier are allowed in it. + """ + + +class _IndividualSpecifier(BaseSpecifier): + + _operators = {} + + def __init__(self, spec="", prereleases=None): + match = self._regex.search(spec) + if not match: + raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) + + self._spec = (match.group("operator").strip(), match.group("version").strip()) + + # Store whether or not this Specifier should accept prereleases + self._prereleases = prereleases + + def __repr__(self): + pre = ( + ", prereleases={0!r}".format(self.prereleases) + if self._prereleases is not None + else "" + ) + + return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre) + + def __str__(self): + return "{0}{1}".format(*self._spec) + + def __hash__(self): + return hash(self._spec) + + def __eq__(self, other): + if isinstance(other, string_types): + try: + other = self.__class__(other) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._spec == other._spec + + def __ne__(self, other): + if isinstance(other, string_types): + try: + other = self.__class__(other) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._spec != other._spec + + def _get_operator(self, op): + return getattr(self, "_compare_{0}".format(self._operators[op])) + + def _coerce_version(self, version): + if not isinstance(version, (LegacyVersion, Version)): + version = parse(version) + return version + + @property + def operator(self): + return self._spec[0] + + @property + def version(self): + return self._spec[1] + + @property + def prereleases(self): + return self._prereleases + + @prereleases.setter + def prereleases(self, value): + self._prereleases = value + + def __contains__(self, item): + return self.contains(item) + + def contains(self, item, prereleases=None): + # Determine if prereleases are to be allowed or not. + if prereleases is None: + prereleases = self.prereleases + + # Normalize item to a Version or LegacyVersion, this allows us to have + # a shortcut for ``"2.0" in Specifier(">=2") + item = self._coerce_version(item) + + # Determine if we should be supporting prereleases in this specifier + # or not, if we do not support prereleases than we can short circuit + # logic if this version is a prereleases. + if item.is_prerelease and not prereleases: + return False + + # Actually do the comparison to determine if this item is contained + # within this Specifier or not. + return self._get_operator(self.operator)(item, self.version) + + def filter(self, iterable, prereleases=None): + yielded = False + found_prereleases = [] + + kw = {"prereleases": prereleases if prereleases is not None else True} + + # Attempt to iterate over all the values in the iterable and if any of + # them match, yield them. + for version in iterable: + parsed_version = self._coerce_version(version) + + if self.contains(parsed_version, **kw): + # If our version is a prerelease, and we were not set to allow + # prereleases, then we'll store it for later incase nothing + # else matches this specifier. + if parsed_version.is_prerelease and not ( + prereleases or self.prereleases + ): + found_prereleases.append(version) + # Either this is not a prerelease, or we should have been + # accepting prereleases from the beginning. + else: + yielded = True + yield version + + # Now that we've iterated over everything, determine if we've yielded + # any values, and if we have not and we have any prereleases stored up + # then we will go ahead and yield the prereleases. + if not yielded and found_prereleases: + for version in found_prereleases: + yield version + + +class LegacySpecifier(_IndividualSpecifier): + + _regex_str = r""" + (?P(==|!=|<=|>=|<|>)) + \s* + (?P + [^,;\s)]* # Since this is a "legacy" specifier, and the version + # string can be just about anything, we match everything + # except for whitespace, a semi-colon for marker support, + # a closing paren since versions can be enclosed in + # them, and a comma since it's a version separator. + ) + """ + + _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + + _operators = { + "==": "equal", + "!=": "not_equal", + "<=": "less_than_equal", + ">=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + } + + def _coerce_version(self, version): + if not isinstance(version, LegacyVersion): + version = LegacyVersion(str(version)) + return version + + def _compare_equal(self, prospective, spec): + return prospective == self._coerce_version(spec) + + def _compare_not_equal(self, prospective, spec): + return prospective != self._coerce_version(spec) + + def _compare_less_than_equal(self, prospective, spec): + return prospective <= self._coerce_version(spec) + + def _compare_greater_than_equal(self, prospective, spec): + return prospective >= self._coerce_version(spec) + + def _compare_less_than(self, prospective, spec): + return prospective < self._coerce_version(spec) + + def _compare_greater_than(self, prospective, spec): + return prospective > self._coerce_version(spec) + + +def _require_version_compare(fn): + @functools.wraps(fn) + def wrapped(self, prospective, spec): + if not isinstance(prospective, Version): + return False + return fn(self, prospective, spec) + + return wrapped + + +class Specifier(_IndividualSpecifier): + + _regex_str = r""" + (?P(~=|==|!=|<=|>=|<|>|===)) + (?P + (?: + # The identity operators allow for an escape hatch that will + # do an exact string match of the version you wish to install. + # This will not be parsed by PEP 440 and we cannot determine + # any semantic meaning from it. This operator is discouraged + # but included entirely as an escape hatch. + (?<====) # Only match for the identity operator + \s* + [^\s]* # We just match everything, except for whitespace + # since we are only testing for strict identity. + ) + | + (?: + # The (non)equality operators allow for wild card and local + # versions to be specified so we have to define these two + # operators separately to enable that. + (?<===|!=) # Only match for equals and not equals + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + + # You cannot use a wild card and a dev or local version + # together so group them with a | and make them optional. + (?: + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local + | + \.\* # Wild card syntax of .* + )? + ) + | + (?: + # The compatible operator requires at least two digits in the + # release segment. + (?<=~=) # Only match for the compatible operator + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + | + (?: + # All other operators only allow a sub set of what the + # (non)equality operators do. Specifically they do not allow + # local versions to be specified nor do they allow the prefix + # matching wild cards. + (?=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + "===": "arbitrary", + } + + @_require_version_compare + def _compare_compatible(self, prospective, spec): + # Compatible releases have an equivalent combination of >= and ==. That + # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to + # implement this in terms of the other specifiers instead of + # implementing it ourselves. The only thing we need to do is construct + # the other specifiers. + + # We want everything but the last item in the version, but we want to + # ignore post and dev releases and we want to treat the pre-release as + # it's own separate segment. + prefix = ".".join( + list( + itertools.takewhile( + lambda x: (not x.startswith("post") and not x.startswith("dev")), + _version_split(spec), + ) + )[:-1] + ) + + # Add the prefix notation to the end of our string + prefix += ".*" + + return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( + prospective, prefix + ) + + @_require_version_compare + def _compare_equal(self, prospective, spec): + # We need special logic to handle prefix matching + if spec.endswith(".*"): + # In the case of prefix matching we want to ignore local segment. + prospective = Version(prospective.public) + # Split the spec out by dots, and pretend that there is an implicit + # dot in between a release segment and a pre-release segment. + spec = _version_split(spec[:-2]) # Remove the trailing .* + + # Split the prospective version out by dots, and pretend that there + # is an implicit dot in between a release segment and a pre-release + # segment. + prospective = _version_split(str(prospective)) + + # Shorten the prospective version to be the same length as the spec + # so that we can determine if the specifier is a prefix of the + # prospective version or not. + prospective = prospective[: len(spec)] + + # Pad out our two sides with zeros so that they both equal the same + # length. + spec, prospective = _pad_version(spec, prospective) + else: + # Convert our spec string into a Version + spec = Version(spec) + + # If the specifier does not have a local segment, then we want to + # act as if the prospective version also does not have a local + # segment. + if not spec.local: + prospective = Version(prospective.public) + + return prospective == spec + + @_require_version_compare + def _compare_not_equal(self, prospective, spec): + return not self._compare_equal(prospective, spec) + + @_require_version_compare + def _compare_less_than_equal(self, prospective, spec): + return prospective <= Version(spec) + + @_require_version_compare + def _compare_greater_than_equal(self, prospective, spec): + return prospective >= Version(spec) + + @_require_version_compare + def _compare_less_than(self, prospective, spec): + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec) + + # Check to see if the prospective version is less than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective < spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a pre-release version, that we do not accept pre-release + # versions for the version mentioned in the specifier (e.g. <3.1 should + # not match 3.1.dev0, but should match 3.0.dev0). + if not spec.is_prerelease and prospective.is_prerelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # less than the spec version *and* it's not a pre-release of the same + # version in the spec. + return True + + @_require_version_compare + def _compare_greater_than(self, prospective, spec): + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec) + + # Check to see if the prospective version is greater than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective > spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a post-release version, that we do not accept + # post-release versions for the version mentioned in the specifier + # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + if not spec.is_postrelease and prospective.is_postrelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # Ensure that we do not allow a local version of the version mentioned + # in the specifier, which is technically greater than, to match. + if prospective.local is not None: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # greater than the spec version *and* it's not a pre-release of the + # same version in the spec. + return True + + def _compare_arbitrary(self, prospective, spec): + return str(prospective).lower() == str(spec).lower() + + @property + def prereleases(self): + # If there is an explicit prereleases set for this, then we'll just + # blindly use that. + if self._prereleases is not None: + return self._prereleases + + # Look at all of our specifiers and determine if they are inclusive + # operators, and if they are if they are including an explicit + # prerelease. + operator, version = self._spec + if operator in ["==", ">=", "<=", "~=", "==="]: + # The == specifier can include a trailing .*, if it does we + # want to remove before parsing. + if operator == "==" and version.endswith(".*"): + version = version[:-2] + + # Parse the version, and if it is a pre-release than this + # specifier allows pre-releases. + if parse(version).is_prerelease: + return True + + return False + + @prereleases.setter + def prereleases(self, value): + self._prereleases = value + + +_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") + + +def _version_split(version): + result = [] + for item in version.split("."): + match = _prefix_regex.search(item) + if match: + result.extend(match.groups()) + else: + result.append(item) + return result + + +def _pad_version(left, right): + left_split, right_split = [], [] + + # Get the release segment of our versions + left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) + right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) + + # Get the rest of our versions + left_split.append(left[len(left_split[0]) :]) + right_split.append(right[len(right_split[0]) :]) + + # Insert our padding + left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) + right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) + + return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split))) + + +class SpecifierSet(BaseSpecifier): + def __init__(self, specifiers="", prereleases=None): + # Split on , to break each indidivual specifier into it's own item, and + # strip each item to remove leading/trailing whitespace. + specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + + # Parsed each individual specifier, attempting first to make it a + # Specifier and falling back to a LegacySpecifier. + parsed = set() + for specifier in specifiers: + try: + parsed.add(Specifier(specifier)) + except InvalidSpecifier: + parsed.add(LegacySpecifier(specifier)) + + # Turn our parsed specifiers into a frozen set and save them for later. + self._specs = frozenset(parsed) + + # Store our prereleases value so we can use it later to determine if + # we accept prereleases or not. + self._prereleases = prereleases + + def __repr__(self): + pre = ( + ", prereleases={0!r}".format(self.prereleases) + if self._prereleases is not None + else "" + ) + + return "".format(str(self), pre) + + def __str__(self): + return ",".join(sorted(str(s) for s in self._specs)) + + def __hash__(self): + return hash(self._specs) + + def __and__(self, other): + if isinstance(other, string_types): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifier = SpecifierSet() + specifier._specs = frozenset(self._specs | other._specs) + + if self._prereleases is None and other._prereleases is not None: + specifier._prereleases = other._prereleases + elif self._prereleases is not None and other._prereleases is None: + specifier._prereleases = self._prereleases + elif self._prereleases == other._prereleases: + specifier._prereleases = self._prereleases + else: + raise ValueError( + "Cannot combine SpecifierSets with True and False prerelease " + "overrides." + ) + + return specifier + + def __eq__(self, other): + if isinstance(other, string_types): + other = SpecifierSet(other) + elif isinstance(other, _IndividualSpecifier): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs == other._specs + + def __ne__(self, other): + if isinstance(other, string_types): + other = SpecifierSet(other) + elif isinstance(other, _IndividualSpecifier): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs != other._specs + + def __len__(self): + return len(self._specs) + + def __iter__(self): + return iter(self._specs) + + @property + def prereleases(self): + # If we have been given an explicit prerelease modifier, then we'll + # pass that through here. + if self._prereleases is not None: + return self._prereleases + + # If we don't have any specifiers, and we don't have a forced value, + # then we'll just return None since we don't know if this should have + # pre-releases or not. + if not self._specs: + return None + + # Otherwise we'll see if any of the given specifiers accept + # prereleases, if any of them do we'll return True, otherwise False. + return any(s.prereleases for s in self._specs) + + @prereleases.setter + def prereleases(self, value): + self._prereleases = value + + def __contains__(self, item): + return self.contains(item) + + def contains(self, item, prereleases=None): + # Ensure that our item is a Version or LegacyVersion instance. + if not isinstance(item, (LegacyVersion, Version)): + item = parse(item) + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # We can determine if we're going to allow pre-releases by looking to + # see if any of the underlying items supports them. If none of them do + # and this item is a pre-release then we do not allow it and we can + # short circuit that here. + # Note: This means that 1.0.dev1 would not be contained in something + # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 + if not prereleases and item.is_prerelease: + return False + + # We simply dispatch to the underlying specs here to make sure that the + # given version is contained within all of them. + # Note: This use of all() here means that an empty set of specifiers + # will always return True, this is an explicit design decision. + return all(s.contains(item, prereleases=prereleases) for s in self._specs) + + def filter(self, iterable, prereleases=None): + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # If we have any specifiers, then we want to wrap our iterable in the + # filter method for each one, this will act as a logical AND amongst + # each specifier. + if self._specs: + for spec in self._specs: + iterable = spec.filter(iterable, prereleases=bool(prereleases)) + return iterable + # If we do not have any specifiers, then we need to have a rough filter + # which will filter out any pre-releases, unless there are no final + # releases, and which will filter out LegacyVersion in general. + else: + filtered = [] + found_prereleases = [] + + for item in iterable: + # Ensure that we some kind of Version class for this item. + if not isinstance(item, (LegacyVersion, Version)): + parsed_version = parse(item) + else: + parsed_version = item + + # Filter out any item which is parsed as a LegacyVersion + if isinstance(parsed_version, LegacyVersion): + continue + + # Store any item which is a pre-release for later unless we've + # already found a final version or we are accepting prereleases + if parsed_version.is_prerelease and not prereleases: + if not filtered: + found_prereleases.append(item) + else: + filtered.append(item) + + # If we've found no items except for pre-releases, then we'll go + # ahead and use the pre-releases + if not filtered and found_prereleases and prereleases is None: + return found_prereleases + + return filtered diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/tags.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/tags.py new file mode 100644 index 0000000000000000000000000000000000000000..ec9942f0f6627f34554082a8c0909bc70bd2a260 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/tags.py @@ -0,0 +1,404 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import + +import distutils.util + +try: + from importlib.machinery import EXTENSION_SUFFIXES +except ImportError: # pragma: no cover + import imp + + EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()] + del imp +import platform +import re +import sys +import sysconfig +import warnings + + +INTERPRETER_SHORT_NAMES = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} + + +_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 + + +class Tag(object): + + __slots__ = ["_interpreter", "_abi", "_platform"] + + def __init__(self, interpreter, abi, platform): + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + + @property + def interpreter(self): + return self._interpreter + + @property + def abi(self): + return self._abi + + @property + def platform(self): + return self._platform + + def __eq__(self, other): + return ( + (self.platform == other.platform) + and (self.abi == other.abi) + and (self.interpreter == other.interpreter) + ) + + def __hash__(self): + return hash((self._interpreter, self._abi, self._platform)) + + def __str__(self): + return "{}-{}-{}".format(self._interpreter, self._abi, self._platform) + + def __repr__(self): + return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) + + +def parse_tag(tag): + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _normalize_string(string): + return string.replace(".", "_").replace("-", "_") + + +def _cpython_interpreter(py_version): + # TODO: Is using py_version_nodot for interpreter version critical? + return "cp{major}{minor}".format(major=py_version[0], minor=py_version[1]) + + +def _cpython_abis(py_version): + abis = [] + version = "{}{}".format(*py_version[:2]) + debug = pymalloc = ucs4 = "" + with_debug = sysconfig.get_config_var("Py_DEBUG") + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version < (3, 8): + with_pymalloc = sysconfig.get_config_var("WITH_PYMALLOC") + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = sysconfig.get_config_var("Py_UNICODE_SIZE") + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append("cp{version}".format(version=version)) + abis.insert( + 0, + "cp{version}{debug}{pymalloc}{ucs4}".format( + version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 + ), + ) + return abis + + +def _cpython_tags(py_version, interpreter, abis, platforms): + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): + yield tag + for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms): + yield tag + # PEP 384 was first implemented in Python 3.2. + for minor_version in range(py_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{major}{minor}".format( + major=py_version[0], minor=minor_version + ) + yield Tag(interpreter, "abi3", platform_) + + +def _pypy_interpreter(): + return "pp{py_major}{pypy_major}{pypy_minor}".format( + py_major=sys.version_info[0], + pypy_major=sys.pypy_version_info.major, + pypy_minor=sys.pypy_version_info.minor, + ) + + +def _generic_abi(): + abi = sysconfig.get_config_var("SOABI") + if abi: + return _normalize_string(abi) + else: + return "none" + + +def _pypy_tags(py_version, interpreter, abi, platforms): + for tag in (Tag(interpreter, abi, platform) for platform in platforms): + yield tag + for tag in (Tag(interpreter, "none", platform) for platform in platforms): + yield tag + + +def _generic_tags(interpreter, py_version, abi, platforms): + for tag in (Tag(interpreter, abi, platform) for platform in platforms): + yield tag + if abi != "none": + tags = (Tag(interpreter, "none", platform_) for platform_ in platforms) + for tag in tags: + yield tag + + +def _py_interpreter_range(py_version): + """ + Yield Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all following versions up to 'end'. + """ + yield "py{major}{minor}".format(major=py_version[0], minor=py_version[1]) + yield "py{major}".format(major=py_version[0]) + for minor in range(py_version[1] - 1, -1, -1): + yield "py{major}{minor}".format(major=py_version[0], minor=minor) + + +def _independent_tags(interpreter, py_version, platforms): + """ + Return the sequence of tags that are consistent across implementations. + + The tags consist of: + - py*-none- + - -none-any + - py*-none-any + """ + for version in _py_interpreter_range(py_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(py_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER): + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version, cpu_arch): + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + formats.append("universal") + return formats + + +def _mac_platforms(version=None, arch=None): + version_str, _, cpu_arch = platform.mac_ver() + if version is None: + version = tuple(map(int, version_str.split(".")[:2])) + if arch is None: + arch = _mac_arch(cpu_arch) + platforms = [] + for minor_version in range(version[1], -1, -1): + compat_version = version[0], minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + platforms.append( + "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + ) + return platforms + + +# From PEP 513. +def _is_manylinux_compatible(name, glibc_version): + # Check for presence of _manylinux module. + try: + import _manylinux + + return bool(getattr(_manylinux, name + "_compatible")) + except (ImportError, AttributeError): + # Fall through to heuristic check below. + pass + + return _have_compatible_glibc(*glibc_version) + + +def _glibc_version_string(): + # Returns glibc version string, or None if not using glibc. + import ctypes + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + process_namespace = ctypes.CDLL(None) + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +# Separated out from have_compatible_glibc for easier unit testing. +def _check_glibc_version(version_str, required_major, minimum_minor): + # Parse string and check against requested version. + # + # We use a regexp instead of str.split because we want to discard any + # random junk that might come after the minor version -- this might happen + # in patched/forked versions of glibc (e.g. Linaro's version of glibc + # uses version strings like "2.20-2014.11"). See gh-3588. + m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) + if not m: + warnings.warn( + "Expected glibc version with 2 components major.minor," + " got: %s" % version_str, + RuntimeWarning, + ) + return False + return ( + int(m.group("major")) == required_major + and int(m.group("minor")) >= minimum_minor + ) + + +def _have_compatible_glibc(required_major, minimum_minor): + version_str = _glibc_version_string() + if version_str is None: + return False + return _check_glibc_version(version_str, required_major, minimum_minor) + + +def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): + linux = _normalize_string(distutils.util.get_platform()) + if linux == "linux_x86_64" and is_32bit: + linux = "linux_i686" + manylinux_support = ( + ("manylinux2014", (2, 17)), # CentOS 7 w/ glibc 2.17 (PEP 599) + ("manylinux2010", (2, 12)), # CentOS 6 w/ glibc 2.12 (PEP 571) + ("manylinux1", (2, 5)), # CentOS 5 w/ glibc 2.5 (PEP 513) + ) + manylinux_support_iter = iter(manylinux_support) + for name, glibc_version in manylinux_support_iter: + if _is_manylinux_compatible(name, glibc_version): + platforms = [linux.replace("linux", name)] + break + else: + platforms = [] + # Support for a later manylinux implies support for an earlier version. + platforms += [linux.replace("linux", name) for name, _ in manylinux_support_iter] + platforms.append(linux) + return platforms + + +def _generic_platforms(): + platform = _normalize_string(distutils.util.get_platform()) + return [platform] + + +def _interpreter_name(): + name = platform.python_implementation().lower() + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def _generic_interpreter(name, py_version): + version = sysconfig.get_config_var("py_version_nodot") + if not version: + version = "".join(map(str, py_version[:2])) + return "{name}{version}".format(name=name, version=version) + + +def sys_tags(): + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + py_version = sys.version_info[:2] + interpreter_name = _interpreter_name() + if platform.system() == "Darwin": + platforms = _mac_platforms() + elif platform.system() == "Linux": + platforms = _linux_platforms() + else: + platforms = _generic_platforms() + + if interpreter_name == "cp": + interpreter = _cpython_interpreter(py_version) + abis = _cpython_abis(py_version) + for tag in _cpython_tags(py_version, interpreter, abis, platforms): + yield tag + elif interpreter_name == "pp": + interpreter = _pypy_interpreter() + abi = _generic_abi() + for tag in _pypy_tags(py_version, interpreter, abi, platforms): + yield tag + else: + interpreter = _generic_interpreter(interpreter_name, py_version) + abi = _generic_abi() + for tag in _generic_tags(interpreter, py_version, abi, platforms): + yield tag + for tag in _independent_tags(interpreter, py_version, platforms): + yield tag diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/utils.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..88418786933b8bc5f6179b8e191f60f79efd7074 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/utils.py @@ -0,0 +1,57 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import re + +from .version import InvalidVersion, Version + + +_canonicalize_regex = re.compile(r"[-_.]+") + + +def canonicalize_name(name): + # This is taken from PEP 503. + return _canonicalize_regex.sub("-", name).lower() + + +def canonicalize_version(version): + """ + This is very similar to Version.__str__, but has one subtle differences + with the way it handles the release segment. + """ + + try: + version = Version(version) + except InvalidVersion: + # Legacy versions cannot be normalized + return version + + parts = [] + + # Epoch + if version.epoch != 0: + parts.append("{0}!".format(version.epoch)) + + # Release segment + # NB: This strips trailing '.0's to normalize + parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release))) + + # Pre-release + if version.pre is not None: + parts.append("".join(str(x) for x in version.pre)) + + # Post-release + if version.post is not None: + parts.append(".post{0}".format(version.post)) + + # Development release + if version.dev is not None: + parts.append(".dev{0}".format(version.dev)) + + # Local version segment + if version.local is not None: + parts.append("+{0}".format(version.local)) + + return "".join(parts) diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/version.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/version.py new file mode 100644 index 0000000000000000000000000000000000000000..95157a1f78c26829ffbe1bd2463f7735b636d16f --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/packaging/version.py @@ -0,0 +1,420 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import collections +import itertools +import re + +from ._structures import Infinity + + +__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] + + +_Version = collections.namedtuple( + "_Version", ["epoch", "release", "dev", "pre", "post", "local"] +) + + +def parse(version): + """ + Parse the given version string and return either a :class:`Version` object + or a :class:`LegacyVersion` object depending on if the given version is + a valid PEP 440 version or a legacy version. + """ + try: + return Version(version) + except InvalidVersion: + return LegacyVersion(version) + + +class InvalidVersion(ValueError): + """ + An invalid version was found, users should refer to PEP 440. + """ + + +class _BaseVersion(object): + def __hash__(self): + return hash(self._key) + + def __lt__(self, other): + return self._compare(other, lambda s, o: s < o) + + def __le__(self, other): + return self._compare(other, lambda s, o: s <= o) + + def __eq__(self, other): + return self._compare(other, lambda s, o: s == o) + + def __ge__(self, other): + return self._compare(other, lambda s, o: s >= o) + + def __gt__(self, other): + return self._compare(other, lambda s, o: s > o) + + def __ne__(self, other): + return self._compare(other, lambda s, o: s != o) + + def _compare(self, other, method): + if not isinstance(other, _BaseVersion): + return NotImplemented + + return method(self._key, other._key) + + +class LegacyVersion(_BaseVersion): + def __init__(self, version): + self._version = str(version) + self._key = _legacy_cmpkey(self._version) + + def __str__(self): + return self._version + + def __repr__(self): + return "".format(repr(str(self))) + + @property + def public(self): + return self._version + + @property + def base_version(self): + return self._version + + @property + def epoch(self): + return -1 + + @property + def release(self): + return None + + @property + def pre(self): + return None + + @property + def post(self): + return None + + @property + def dev(self): + return None + + @property + def local(self): + return None + + @property + def is_prerelease(self): + return False + + @property + def is_postrelease(self): + return False + + @property + def is_devrelease(self): + return False + + +_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) + +_legacy_version_replacement_map = { + "pre": "c", + "preview": "c", + "-": "final-", + "rc": "c", + "dev": "@", +} + + +def _parse_version_parts(s): + for part in _legacy_version_component_re.split(s): + part = _legacy_version_replacement_map.get(part, part) + + if not part or part == ".": + continue + + if part[:1] in "0123456789": + # pad for numeric comparison + yield part.zfill(8) + else: + yield "*" + part + + # ensure that alpha/beta/candidate are before final + yield "*final" + + +def _legacy_cmpkey(version): + # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch + # greater than or equal to 0. This will effectively put the LegacyVersion, + # which uses the defacto standard originally implemented by setuptools, + # as before all PEP 440 versions. + epoch = -1 + + # This scheme is taken from pkg_resources.parse_version setuptools prior to + # it's adoption of the packaging library. + parts = [] + for part in _parse_version_parts(version.lower()): + if part.startswith("*"): + # remove "-" before a prerelease tag + if part < "*final": + while parts and parts[-1] == "*final-": + parts.pop() + + # remove trailing zeros from each series of numeric parts + while parts and parts[-1] == "00000000": + parts.pop() + + parts.append(part) + parts = tuple(parts) + + return epoch, parts + + +# Deliberately not anchored to the start and end of the string, to make it +# easier for 3rd party code to reuse +VERSION_PATTERN = r""" + v? + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P
                                              # pre-release
    +            [-_\.]?
    +            (?P(a|b|c|rc|alpha|beta|pre|preview))
    +            [-_\.]?
    +            (?P[0-9]+)?
    +        )?
    +        (?P                                         # post release
    +            (?:-(?P[0-9]+))
    +            |
    +            (?:
    +                [-_\.]?
    +                (?Ppost|rev|r)
    +                [-_\.]?
    +                (?P[0-9]+)?
    +            )
    +        )?
    +        (?P                                          # dev release
    +            [-_\.]?
    +            (?Pdev)
    +            [-_\.]?
    +            (?P[0-9]+)?
    +        )?
    +    )
    +    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
    +"""
    +
    +
    +class Version(_BaseVersion):
    +
    +    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
    +
    +    def __init__(self, version):
    +        # Validate the version and parse it into pieces
    +        match = self._regex.search(version)
    +        if not match:
    +            raise InvalidVersion("Invalid version: '{0}'".format(version))
    +
    +        # Store the parsed out pieces of the version
    +        self._version = _Version(
    +            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
    +            release=tuple(int(i) for i in match.group("release").split(".")),
    +            pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
    +            post=_parse_letter_version(
    +                match.group("post_l"), match.group("post_n1") or match.group("post_n2")
    +            ),
    +            dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
    +            local=_parse_local_version(match.group("local")),
    +        )
    +
    +        # Generate a key which will be used for sorting
    +        self._key = _cmpkey(
    +            self._version.epoch,
    +            self._version.release,
    +            self._version.pre,
    +            self._version.post,
    +            self._version.dev,
    +            self._version.local,
    +        )
    +
    +    def __repr__(self):
    +        return "".format(repr(str(self)))
    +
    +    def __str__(self):
    +        parts = []
    +
    +        # Epoch
    +        if self.epoch != 0:
    +            parts.append("{0}!".format(self.epoch))
    +
    +        # Release segment
    +        parts.append(".".join(str(x) for x in self.release))
    +
    +        # Pre-release
    +        if self.pre is not None:
    +            parts.append("".join(str(x) for x in self.pre))
    +
    +        # Post-release
    +        if self.post is not None:
    +            parts.append(".post{0}".format(self.post))
    +
    +        # Development release
    +        if self.dev is not None:
    +            parts.append(".dev{0}".format(self.dev))
    +
    +        # Local version segment
    +        if self.local is not None:
    +            parts.append("+{0}".format(self.local))
    +
    +        return "".join(parts)
    +
    +    @property
    +    def epoch(self):
    +        return self._version.epoch
    +
    +    @property
    +    def release(self):
    +        return self._version.release
    +
    +    @property
    +    def pre(self):
    +        return self._version.pre
    +
    +    @property
    +    def post(self):
    +        return self._version.post[1] if self._version.post else None
    +
    +    @property
    +    def dev(self):
    +        return self._version.dev[1] if self._version.dev else None
    +
    +    @property
    +    def local(self):
    +        if self._version.local:
    +            return ".".join(str(x) for x in self._version.local)
    +        else:
    +            return None
    +
    +    @property
    +    def public(self):
    +        return str(self).split("+", 1)[0]
    +
    +    @property
    +    def base_version(self):
    +        parts = []
    +
    +        # Epoch
    +        if self.epoch != 0:
    +            parts.append("{0}!".format(self.epoch))
    +
    +        # Release segment
    +        parts.append(".".join(str(x) for x in self.release))
    +
    +        return "".join(parts)
    +
    +    @property
    +    def is_prerelease(self):
    +        return self.dev is not None or self.pre is not None
    +
    +    @property
    +    def is_postrelease(self):
    +        return self.post is not None
    +
    +    @property
    +    def is_devrelease(self):
    +        return self.dev is not None
    +
    +
    +def _parse_letter_version(letter, number):
    +    if letter:
    +        # We consider there to be an implicit 0 in a pre-release if there is
    +        # not a numeral associated with it.
    +        if number is None:
    +            number = 0
    +
    +        # We normalize any letters to their lower case form
    +        letter = letter.lower()
    +
    +        # We consider some words to be alternate spellings of other words and
    +        # in those cases we want to normalize the spellings to our preferred
    +        # spelling.
    +        if letter == "alpha":
    +            letter = "a"
    +        elif letter == "beta":
    +            letter = "b"
    +        elif letter in ["c", "pre", "preview"]:
    +            letter = "rc"
    +        elif letter in ["rev", "r"]:
    +            letter = "post"
    +
    +        return letter, int(number)
    +    if not letter and number:
    +        # We assume if we are given a number, but we are not given a letter
    +        # then this is using the implicit post release syntax (e.g. 1.0-1)
    +        letter = "post"
    +
    +        return letter, int(number)
    +
    +
    +_local_version_separators = re.compile(r"[\._-]")
    +
    +
    +def _parse_local_version(local):
    +    """
    +    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
    +    """
    +    if local is not None:
    +        return tuple(
    +            part.lower() if not part.isdigit() else int(part)
    +            for part in _local_version_separators.split(local)
    +        )
    +
    +
    +def _cmpkey(epoch, release, pre, post, dev, local):
    +    # When we compare a release version, we want to compare it with all of the
    +    # trailing zeros removed. So we'll use a reverse the list, drop all the now
    +    # leading zeros until we come to something non zero, then take the rest
    +    # re-reverse it back into the correct order and make it a tuple and use
    +    # that for our sorting key.
    +    release = tuple(
    +        reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
    +    )
    +
    +    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
    +    # We'll do this by abusing the pre segment, but we _only_ want to do this
    +    # if there is not a pre or a post segment. If we have one of those then
    +    # the normal sorting rules will handle this case correctly.
    +    if pre is None and post is None and dev is not None:
    +        pre = -Infinity
    +    # Versions without a pre-release (except as noted above) should sort after
    +    # those with one.
    +    elif pre is None:
    +        pre = Infinity
    +
    +    # Versions without a post segment should sort before those with one.
    +    if post is None:
    +        post = -Infinity
    +
    +    # Versions without a development segment should sort after those with one.
    +    if dev is None:
    +        dev = Infinity
    +
    +    if local is None:
    +        # Versions without a local segment should sort before those with one.
    +        local = -Infinity
    +    else:
    +        # Versions with a local segment need that segment parsed to implement
    +        # the sorting rules in PEP440.
    +        # - Alpha numeric segments sort before numeric segments
    +        # - Alpha numeric segments sort lexicographically
    +        # - Numeric segments sort numerically
    +        # - Shorter versions sort before longer versions when the prefixes
    +        #   match exactly
    +        local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local)
    +
    +    return epoch, release, pre, post, dev, local
    diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/pyparsing.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/pyparsing.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..cf75e1e5fcbfe7eac41d2a9e446c5c980741087b
    --- /dev/null
    +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/pyparsing.py
    @@ -0,0 +1,5742 @@
    +# module pyparsing.py
    +#
    +# Copyright (c) 2003-2018  Paul T. McGuire
    +#
    +# Permission is hereby granted, free of charge, to any person obtaining
    +# a copy of this software and associated documentation files (the
    +# "Software"), to deal in the Software without restriction, including
    +# without limitation the rights to use, copy, modify, merge, publish,
    +# distribute, sublicense, and/or sell copies of the Software, and to
    +# permit persons to whom the Software is furnished to do so, subject to
    +# the following conditions:
    +#
    +# The above copyright notice and this permission notice shall be
    +# included in all copies or substantial portions of the Software.
    +#
    +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +#
    +
    +__doc__ = \
    +"""
    +pyparsing module - 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.
    +
    +Here is a program to parse "Hello, World!" (or any greeting of the form 
    +C{", !"}), built up using L{Word}, L{Literal}, and L{And} elements 
    +(L{'+'} operator gives L{And} expressions, strings are auto-converted to
    +L{Literal} expressions)::
    +
    +    from pyparsing import Word, alphas
    +
    +    # define grammar of a greeting
    +    greet = Word(alphas) + "," + Word(alphas) + "!"
    +
    +    hello = "Hello, World!"
    +    print (hello, "->", greet.parseString(hello))
    +
    +The program outputs the following::
    +
    +    Hello, World! -> ['Hello', ',', 'World', '!']
    +
    +The Python representation of the grammar is quite readable, owing to the self-explanatory
    +class names, and the use of '+', '|' and '^' operators.
    +
    +The L{ParseResults} object returned from L{ParserElement.parseString} can be accessed as a nested list, a dictionary, or an
    +object with named attributes.
    +
    +The pyparsing module handles some of the problems that are typically vexing when writing text parsers:
    + - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello  ,  World  !", etc.)
    + - quoted strings
    + - embedded comments
    +
    +
    +Getting Started -
    +-----------------
    +Visit the classes L{ParserElement} and L{ParseResults} to see the base classes that most other pyparsing
    +classes inherit from. Use the docstrings for examples of how to:
    + - construct literal match expressions from L{Literal} and L{CaselessLiteral} classes
    + - construct character word-group expressions using the L{Word} class
    + - see how to create repetitive expressions using L{ZeroOrMore} and L{OneOrMore} classes
    + - use L{'+'}, L{'|'}, L{'^'}, and L{'&'} operators to combine simple expressions into more complex ones
    + - associate names with your parsed results using L{ParserElement.setResultsName}
    + - find some helpful expression short-cuts like L{delimitedList} and L{oneOf}
    + - find more useful common expressions in the L{pyparsing_common} namespace class
    +"""
    +
    +__version__ = "2.2.1"
    +__versionTime__ = "18 Sep 2018 00:49 UTC"
    +__author__ = "Paul McGuire "
    +
    +import string
    +from weakref import ref as wkref
    +import copy
    +import sys
    +import warnings
    +import re
    +import sre_constants
    +import collections
    +import pprint
    +import traceback
    +import types
    +from datetime import datetime
    +
    +try:
    +    from _thread import RLock
    +except ImportError:
    +    from threading import RLock
    +
    +try:
    +    # Python 3
    +    from collections.abc import Iterable
    +    from collections.abc import MutableMapping
    +except ImportError:
    +    # Python 2.7
    +    from collections import Iterable
    +    from collections import MutableMapping
    +
    +try:
    +    from collections import OrderedDict as _OrderedDict
    +except ImportError:
    +    try:
    +        from ordereddict import OrderedDict as _OrderedDict
    +    except ImportError:
    +        _OrderedDict = None
    +
    +#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) )
    +
    +__all__ = [
    +'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty',
    +'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal',
    +'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or',
    +'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException',
    +'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException',
    +'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 
    +'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore',
    +'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col',
    +'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString',
    +'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums',
    +'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno',
    +'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral',
    +'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables',
    +'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', 
    +'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd',
    +'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute',
    +'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation','locatedExpr', 'withClass',
    +'CloseMatch', 'tokenMap', 'pyparsing_common',
    +]
    +
    +system_version = tuple(sys.version_info)[:3]
    +PY_3 = system_version[0] == 3
    +if PY_3:
    +    _MAX_INT = sys.maxsize
    +    basestring = str
    +    unichr = chr
    +    _ustr = str
    +
    +    # build list of single arg builtins, that can be used as parse actions
    +    singleArgBuiltins = [sum, len, sorted, reversed, list, tuple, set, any, all, min, max]
    +
    +else:
    +    _MAX_INT = sys.maxint
    +    range = xrange
    +
    +    def _ustr(obj):
    +        """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries
    +           str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It
    +           then < returns the unicode object | encodes it with the default encoding | ... >.
    +        """
    +        if isinstance(obj,unicode):
    +            return obj
    +
    +        try:
    +            # If this works, then _ustr(obj) has the same behaviour as str(obj), so
    +            # it won't break any existing code.
    +            return str(obj)
    +
    +        except UnicodeEncodeError:
    +            # Else encode it
    +            ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace')
    +            xmlcharref = Regex(r'&#\d+;')
    +            xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:])
    +            return xmlcharref.transformString(ret)
    +
    +    # build list of single arg builtins, tolerant of Python version, that can be used as parse actions
    +    singleArgBuiltins = []
    +    import __builtin__
    +    for fname in "sum len sorted reversed list tuple set any all min max".split():
    +        try:
    +            singleArgBuiltins.append(getattr(__builtin__,fname))
    +        except AttributeError:
    +            continue
    +            
    +_generatorType = type((y for y in range(1)))
    + 
    +def _xml_escape(data):
    +    """Escape &, <, >, ", ', etc. in a string of data."""
    +
    +    # ampersand must be replaced first
    +    from_symbols = '&><"\''
    +    to_symbols = ('&'+s+';' for s in "amp gt lt quot apos".split())
    +    for from_,to_ in zip(from_symbols, to_symbols):
    +        data = data.replace(from_, to_)
    +    return data
    +
    +class _Constants(object):
    +    pass
    +
    +alphas     = string.ascii_uppercase + string.ascii_lowercase
    +nums       = "0123456789"
    +hexnums    = nums + "ABCDEFabcdef"
    +alphanums  = alphas + nums
    +_bslash    = chr(92)
    +printables = "".join(c for c in string.printable if c not in string.whitespace)
    +
    +class ParseBaseException(Exception):
    +    """base exception class for all parsing runtime exceptions"""
    +    # Performance tuning: we construct a *lot* of these, so keep this
    +    # constructor as small and fast as possible
    +    def __init__( self, pstr, loc=0, msg=None, elem=None ):
    +        self.loc = loc
    +        if msg is None:
    +            self.msg = pstr
    +            self.pstr = ""
    +        else:
    +            self.msg = msg
    +            self.pstr = pstr
    +        self.parserElement = elem
    +        self.args = (pstr, loc, msg)
    +
    +    @classmethod
    +    def _from_exception(cls, pe):
    +        """
    +        internal factory method to simplify creating one type of ParseException 
    +        from another - avoids having __init__ signature conflicts among subclasses
    +        """
    +        return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement)
    +
    +    def __getattr__( self, aname ):
    +        """supported attributes by name are:
    +            - lineno - returns the line number of the exception text
    +            - col - returns the column number of the exception text
    +            - line - returns the line containing the exception text
    +        """
    +        if( aname == "lineno" ):
    +            return lineno( self.loc, self.pstr )
    +        elif( aname in ("col", "column") ):
    +            return col( self.loc, self.pstr )
    +        elif( aname == "line" ):
    +            return line( self.loc, self.pstr )
    +        else:
    +            raise AttributeError(aname)
    +
    +    def __str__( self ):
    +        return "%s (at char %d), (line:%d, col:%d)" % \
    +                ( self.msg, self.loc, self.lineno, self.column )
    +    def __repr__( self ):
    +        return _ustr(self)
    +    def markInputline( self, markerString = ">!<" ):
    +        """Extracts the exception line from the input string, and marks
    +           the location of the exception with a special symbol.
    +        """
    +        line_str = self.line
    +        line_column = self.column - 1
    +        if markerString:
    +            line_str = "".join((line_str[:line_column],
    +                                markerString, line_str[line_column:]))
    +        return line_str.strip()
    +    def __dir__(self):
    +        return "lineno col line".split() + dir(type(self))
    +
    +class ParseException(ParseBaseException):
    +    """
    +    Exception thrown when parse expressions don't match class;
    +    supported attributes by name are:
    +     - lineno - returns the line number of the exception text
    +     - col - returns the column number of the exception text
    +     - line - returns the line containing the exception text
    +        
    +    Example::
    +        try:
    +            Word(nums).setName("integer").parseString("ABC")
    +        except ParseException as pe:
    +            print(pe)
    +            print("column: {}".format(pe.col))
    +            
    +    prints::
    +       Expected integer (at char 0), (line:1, col:1)
    +        column: 1
    +    """
    +    pass
    +
    +class ParseFatalException(ParseBaseException):
    +    """user-throwable exception thrown when inconsistent parse content
    +       is found; stops all parsing immediately"""
    +    pass
    +
    +class ParseSyntaxException(ParseFatalException):
    +    """just like L{ParseFatalException}, but thrown internally when an
    +       L{ErrorStop} ('-' operator) indicates that parsing is to stop 
    +       immediately because an unbacktrackable syntax error has been found"""
    +    pass
    +
    +#~ class ReparseException(ParseBaseException):
    +    #~ """Experimental class - parse actions can raise this exception to cause
    +       #~ pyparsing to reparse the input string:
    +        #~ - with a modified input string, and/or
    +        #~ - with a modified start location
    +       #~ Set the values of the ReparseException in the constructor, and raise the
    +       #~ exception in a parse action to cause pyparsing to use the new string/location.
    +       #~ Setting the values as None causes no change to be made.
    +       #~ """
    +    #~ def __init_( self, newstring, restartLoc ):
    +        #~ self.newParseText = newstring
    +        #~ self.reparseLoc = restartLoc
    +
    +class RecursiveGrammarException(Exception):
    +    """exception thrown by L{ParserElement.validate} if the grammar could be improperly recursive"""
    +    def __init__( self, parseElementList ):
    +        self.parseElementTrace = parseElementList
    +
    +    def __str__( self ):
    +        return "RecursiveGrammarException: %s" % self.parseElementTrace
    +
    +class _ParseResultsWithOffset(object):
    +    def __init__(self,p1,p2):
    +        self.tup = (p1,p2)
    +    def __getitem__(self,i):
    +        return self.tup[i]
    +    def __repr__(self):
    +        return repr(self.tup[0])
    +    def setOffset(self,i):
    +        self.tup = (self.tup[0],i)
    +
    +class ParseResults(object):
    +    """
    +    Structured parse results, to provide multiple means of access to the parsed data:
    +       - as a list (C{len(results)})
    +       - by list index (C{results[0], results[1]}, etc.)
    +       - by attribute (C{results.} - see L{ParserElement.setResultsName})
    +
    +    Example::
    +        integer = Word(nums)
    +        date_str = (integer.setResultsName("year") + '/' 
    +                        + integer.setResultsName("month") + '/' 
    +                        + integer.setResultsName("day"))
    +        # equivalent form:
    +        # date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
    +
    +        # parseString returns a ParseResults object
    +        result = date_str.parseString("1999/12/31")
    +
    +        def test(s, fn=repr):
    +            print("%s -> %s" % (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)
    +    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
    +    """
    +    def __new__(cls, toklist=None, name=None, asList=True, modal=True ):
    +        if isinstance(toklist, cls):
    +            return toklist
    +        retobj = object.__new__(cls)
    +        retobj.__doinit = True
    +        return retobj
    +
    +    # Performance tuning: we construct a *lot* of these, so keep this
    +    # constructor as small and fast as possible
    +    def __init__( self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance ):
    +        if self.__doinit:
    +            self.__doinit = False
    +            self.__name = None
    +            self.__parent = None
    +            self.__accumNames = {}
    +            self.__asList = asList
    +            self.__modal = modal
    +            if toklist is None:
    +                toklist = []
    +            if isinstance(toklist, list):
    +                self.__toklist = toklist[:]
    +            elif isinstance(toklist, _generatorType):
    +                self.__toklist = list(toklist)
    +            else:
    +                self.__toklist = [toklist]
    +            self.__tokdict = dict()
    +
    +        if name is not None and name:
    +            if not modal:
    +                self.__accumNames[name] = 0
    +            if isinstance(name,int):
    +                name = _ustr(name) # will always return a str, but use _ustr for consistency
    +            self.__name = name
    +            if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None,'',[])):
    +                if isinstance(toklist,basestring):
    +                    toklist = [ toklist ]
    +                if asList:
    +                    if isinstance(toklist,ParseResults):
    +                        self[name] = _ParseResultsWithOffset(toklist.copy(),0)
    +                    else:
    +                        self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0)
    +                    self[name].__name = name
    +                else:
    +                    try:
    +                        self[name] = toklist[0]
    +                    except (KeyError,TypeError,IndexError):
    +                        self[name] = toklist
    +
    +    def __getitem__( self, i ):
    +        if isinstance( i, (int,slice) ):
    +            return self.__toklist[i]
    +        else:
    +            if i not in self.__accumNames:
    +                return self.__tokdict[i][-1][0]
    +            else:
    +                return ParseResults([ v[0] for v in self.__tokdict[i] ])
    +
    +    def __setitem__( self, k, v, isinstance=isinstance ):
    +        if isinstance(v,_ParseResultsWithOffset):
    +            self.__tokdict[k] = self.__tokdict.get(k,list()) + [v]
    +            sub = v[0]
    +        elif isinstance(k,(int,slice)):
    +            self.__toklist[k] = v
    +            sub = v
    +        else:
    +            self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)]
    +            sub = v
    +        if isinstance(sub,ParseResults):
    +            sub.__parent = wkref(self)
    +
    +    def __delitem__( self, i ):
    +        if isinstance(i,(int,slice)):
    +            mylen = len( self.__toklist )
    +            del self.__toklist[i]
    +
    +            # convert int to slice
    +            if isinstance(i, int):
    +                if i < 0:
    +                    i += mylen
    +                i = slice(i, i+1)
    +            # get removed indices
    +            removed = list(range(*i.indices(mylen)))
    +            removed.reverse()
    +            # fixup indices in token dictionary
    +            for name,occurrences in self.__tokdict.items():
    +                for j in removed:
    +                    for k, (value, position) in enumerate(occurrences):
    +                        occurrences[k] = _ParseResultsWithOffset(value, position - (position > j))
    +        else:
    +            del self.__tokdict[i]
    +
    +    def __contains__( self, k ):
    +        return k in self.__tokdict
    +
    +    def __len__( self ): return len( self.__toklist )
    +    def __bool__(self): return ( not not self.__toklist )
    +    __nonzero__ = __bool__
    +    def __iter__( self ): return iter( self.__toklist )
    +    def __reversed__( self ): return iter( self.__toklist[::-1] )
    +    def _iterkeys( self ):
    +        if hasattr(self.__tokdict, "iterkeys"):
    +            return self.__tokdict.iterkeys()
    +        else:
    +            return iter(self.__tokdict)
    +
    +    def _itervalues( self ):
    +        return (self[k] for k in self._iterkeys())
    +            
    +    def _iteritems( self ):
    +        return ((k, self[k]) for k in self._iterkeys())
    +
    +    if PY_3:
    +        keys = _iterkeys       
    +        """Returns an iterator of all named result keys (Python 3.x only)."""
    +
    +        values = _itervalues
    +        """Returns an iterator of all named result values (Python 3.x only)."""
    +
    +        items = _iteritems
    +        """Returns an iterator of all named result key-value tuples (Python 3.x only)."""
    +
    +    else:
    +        iterkeys = _iterkeys
    +        """Returns an iterator of all named result keys (Python 2.x only)."""
    +
    +        itervalues = _itervalues
    +        """Returns an iterator of all named result values (Python 2.x only)."""
    +
    +        iteritems = _iteritems
    +        """Returns an iterator of all named result key-value tuples (Python 2.x only)."""
    +
    +        def keys( self ):
    +            """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x)."""
    +            return list(self.iterkeys())
    +
    +        def values( self ):
    +            """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x)."""
    +            return list(self.itervalues())
    +                
    +        def items( self ):
    +            """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x)."""
    +            return list(self.iteritems())
    +
    +    def haskeys( self ):
    +        """Since keys() returns an iterator, this method is helpful in bypassing
    +           code that looks for the existence of any defined results names."""
    +        return bool(self.__tokdict)
    +        
    +    def pop( self, *args, **kwargs):
    +        """
    +        Removes and returns item at specified index (default=C{last}).
    +        Supports both C{list} and C{dict} semantics for C{pop()}. If passed no
    +        argument or an integer argument, it will use C{list} semantics
    +        and pop tokens from the list of parsed tokens. If passed a 
    +        non-integer argument (most likely a string), it will use C{dict}
    +        semantics and pop the corresponding value from any defined 
    +        results names. A second default return value argument is 
    +        supported, just as in C{dict.pop()}.
    +
    +        Example::
    +            def remove_first(tokens):
    +                tokens.pop(0)
    +            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
    +            print(OneOrMore(Word(nums)).addParseAction(remove_first).parseString("0 123 321")) # -> ['123', '321']
    +
    +            label = Word(alphas)
    +            patt = label("LABEL") + OneOrMore(Word(nums))
    +            print(patt.parseString("AAB 123 321").dump())
    +
    +            # 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.addParseAction(remove_LABEL)
    +            print(patt.parseString("AAB 123 321").dump())
    +        prints::
    +            ['AAB', '123', '321']
    +            - LABEL: AAB
    +
    +            ['AAB', '123', '321']
    +        """
    +        if not args:
    +            args = [-1]
    +        for k,v in kwargs.items():
    +            if k == 'default':
    +                args = (args[0], v)
    +            else:
    +                raise TypeError("pop() got an unexpected keyword argument '%s'" % k)
    +        if (isinstance(args[0], int) or 
    +                        len(args) == 1 or 
    +                        args[0] in self):
    +            index = args[0]
    +            ret = self[index]
    +            del self[index]
    +            return ret
    +        else:
    +            defaultvalue = args[1]
    +            return defaultvalue
    +
    +    def get(self, key, defaultValue=None):
    +        """
    +        Returns named result matching the given key, or if there is no
    +        such name, then returns the given C{defaultValue} or C{None} if no
    +        C{defaultValue} is specified.
    +
    +        Similar to C{dict.get()}.
    +        
    +        Example::
    +            integer = Word(nums)
    +            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
    +
    +            result = date_str.parseString("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]
    +        else:
    +            return defaultValue
    +
    +    def insert( self, index, insStr ):
    +        """
    +        Inserts new element at location index in the list of parsed tokens.
    +        
    +        Similar to C{list.insert()}.
    +
    +        Example::
    +            print(OneOrMore(Word(nums)).parseString("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)
    +            print(OneOrMore(Word(nums)).addParseAction(insert_locn).parseString("0 123 321")) # -> [0, '0', '123', '321']
    +        """
    +        self.__toklist.insert(index, insStr)
    +        # fixup indices in token dictionary
    +        for name,occurrences in self.__tokdict.items():
    +            for k, (value, position) in enumerate(occurrences):
    +                occurrences[k] = _ParseResultsWithOffset(value, position + (position > index))
    +
    +    def append( self, item ):
    +        """
    +        Add single element to end of ParseResults list of elements.
    +
    +        Example::
    +            print(OneOrMore(Word(nums)).parseString("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)))
    +            print(OneOrMore(Word(nums)).addParseAction(append_sum).parseString("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.
    +
    +        Example::
    +            patt = OneOrMore(Word(alphas))
    +            
    +            # 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)
    +            print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl'
    +        """
    +        if isinstance(itemseq, ParseResults):
    +            self += itemseq
    +        else:
    +            self.__toklist.extend(itemseq)
    +
    +    def clear( self ):
    +        """
    +        Clear all elements and results names.
    +        """
    +        del self.__toklist[:]
    +        self.__tokdict.clear()
    +
    +    def __getattr__( self, name ):
    +        try:
    +            return self[name]
    +        except KeyError:
    +            return ""
    +            
    +        if name in self.__tokdict:
    +            if name not in self.__accumNames:
    +                return self.__tokdict[name][-1][0]
    +            else:
    +                return ParseResults([ v[0] for v in self.__tokdict[name] ])
    +        else:
    +            return ""
    +
    +    def __add__( self, other ):
    +        ret = self.copy()
    +        ret += other
    +        return ret
    +
    +    def __iadd__( self, other ):
    +        if other.__tokdict:
    +            offset = len(self.__toklist)
    +            addoffset = lambda a: offset if a<0 else a+offset
    +            otheritems = other.__tokdict.items()
    +            otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) )
    +                                for (k,vlist) in otheritems for v in vlist]
    +            for k,v in otherdictitems:
    +                self[k] = v
    +                if isinstance(v[0],ParseResults):
    +                    v[0].__parent = wkref(self)
    +            
    +        self.__toklist += other.__toklist
    +        self.__accumNames.update( other.__accumNames )
    +        return self
    +
    +    def __radd__(self, other):
    +        if isinstance(other,int) and other == 0:
    +            # useful for merging many ParseResults using sum() builtin
    +            return self.copy()
    +        else:
    +            # this may raise a TypeError - so be it
    +            return other + self
    +        
    +    def __repr__( self ):
    +        return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) )
    +
    +    def __str__( self ):
    +        return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']'
    +
    +    def _asStringList( self, sep='' ):
    +        out = []
    +        for item in self.__toklist:
    +            if out and sep:
    +                out.append(sep)
    +            if isinstance( item, ParseResults ):
    +                out += item._asStringList()
    +            else:
    +                out.append( _ustr(item) )
    +        return out
    +
    +    def asList( self ):
    +        """
    +        Returns the parse results as a nested list of matching tokens, all converted to strings.
    +
    +        Example::
    +            patt = OneOrMore(Word(alphas))
    +            result = patt.parseString("sldkj lsdkj sldkj")
    +            # even though the result prints in string-like form, it is actually a pyparsing ParseResults
    +            print(type(result), result) # ->  ['sldkj', 'lsdkj', 'sldkj']
    +            
    +            # Use asList() to create an actual list
    +            result_list = result.asList()
    +            print(type(result_list), result_list) # ->  ['sldkj', 'lsdkj', 'sldkj']
    +        """
    +        return [res.asList() if isinstance(res,ParseResults) else res for res in self.__toklist]
    +
    +    def asDict( self ):
    +        """
    +        Returns the named parse results as a nested dictionary.
    +
    +        Example::
    +            integer = Word(nums)
    +            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
    +            
    +            result = date_str.parseString('12/31/1999')
    +            print(type(result), repr(result)) # ->  (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]})
    +            
    +            result_dict = result.asDict()
    +            print(type(result_dict), repr(result_dict)) # ->  {'day': '1999', 'year': '12', 'month': '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.asDict())) # -> {"month": "31", "day": "1999", "year": "12"}
    +        """
    +        if PY_3:
    +            item_fn = self.items
    +        else:
    +            item_fn = self.iteritems
    +            
    +        def toItem(obj):
    +            if isinstance(obj, ParseResults):
    +                if obj.haskeys():
    +                    return obj.asDict()
    +                else:
    +                    return [toItem(v) for v in obj]
    +            else:
    +                return obj
    +                
    +        return dict((k,toItem(v)) for k,v in item_fn())
    +
    +    def copy( self ):
    +        """
    +        Returns a new copy of a C{ParseResults} object.
    +        """
    +        ret = ParseResults( self.__toklist )
    +        ret.__tokdict = self.__tokdict.copy()
    +        ret.__parent = self.__parent
    +        ret.__accumNames.update( self.__accumNames )
    +        ret.__name = self.__name
    +        return ret
    +
    +    def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ):
    +        """
    +        (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.
    +        """
    +        nl = "\n"
    +        out = []
    +        namedItems = dict((v[1],k) for (k,vlist) in self.__tokdict.items()
    +                                                            for v in vlist)
    +        nextLevelIndent = indent + "  "
    +
    +        # collapse out indents if formatting is not desired
    +        if not formatted:
    +            indent = ""
    +            nextLevelIndent = ""
    +            nl = ""
    +
    +        selfTag = None
    +        if doctag is not None:
    +            selfTag = doctag
    +        else:
    +            if self.__name:
    +                selfTag = self.__name
    +
    +        if not selfTag:
    +            if namedItemsOnly:
    +                return ""
    +            else:
    +                selfTag = "ITEM"
    +
    +        out += [ nl, indent, "<", selfTag, ">" ]
    +
    +        for i,res in enumerate(self.__toklist):
    +            if isinstance(res,ParseResults):
    +                if i in namedItems:
    +                    out += [ res.asXML(namedItems[i],
    +                                        namedItemsOnly and doctag is None,
    +                                        nextLevelIndent,
    +                                        formatted)]
    +                else:
    +                    out += [ res.asXML(None,
    +                                        namedItemsOnly and doctag is None,
    +                                        nextLevelIndent,
    +                                        formatted)]
    +            else:
    +                # individual token, see if there is a name for it
    +                resTag = None
    +                if i in namedItems:
    +                    resTag = namedItems[i]
    +                if not resTag:
    +                    if namedItemsOnly:
    +                        continue
    +                    else:
    +                        resTag = "ITEM"
    +                xmlBodyText = _xml_escape(_ustr(res))
    +                out += [ nl, nextLevelIndent, "<", resTag, ">",
    +                                                xmlBodyText,
    +                                                "" ]
    +
    +        out += [ nl, indent, "" ]
    +        return "".join(out)
    +
    +    def __lookup(self,sub):
    +        for k,vlist in self.__tokdict.items():
    +            for v,loc in vlist:
    +                if sub is v:
    +                    return k
    +        return None
    +
    +    def getName(self):
    +        r"""
    +        Returns the results name for this token expression. Useful when several 
    +        different expressions might match at a particular location.
    +
    +        Example::
    +            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 = OneOrMore(user_data)
    +            
    +            result = user_info.parseString("22 111-22-3333 #221B")
    +            for item in result:
    +                print(item.getName(), ':', item[0])
    +        prints::
    +            age : 22
    +            ssn : 111-22-3333
    +            house_number : 221B
    +        """
    +        if self.__name:
    +            return self.__name
    +        elif self.__parent:
    +            par = self.__parent()
    +            if par:
    +                return par.__lookup(self)
    +            else:
    +                return None
    +        elif (len(self) == 1 and
    +               len(self.__tokdict) == 1 and
    +               next(iter(self.__tokdict.values()))[0][1] in (0,-1)):
    +            return next(iter(self.__tokdict.keys()))
    +        else:
    +            return None
    +
    +    def dump(self, indent='', depth=0, full=True):
    +        """
    +        Diagnostic method for listing out the contents of a C{ParseResults}.
    +        Accepts an optional C{indent} argument so that this string can be embedded
    +        in a nested display of other data.
    +
    +        Example::
    +            integer = Word(nums)
    +            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
    +            
    +            result = date_str.parseString('12/31/1999')
    +            print(result.dump())
    +        prints::
    +            ['12', '/', '31', '/', '1999']
    +            - day: 1999
    +            - month: 31
    +            - year: 12
    +        """
    +        out = []
    +        NL = '\n'
    +        out.append( indent+_ustr(self.asList()) )
    +        if full:
    +            if self.haskeys():
    +                items = sorted((str(k), v) for k,v in self.items())
    +                for k,v in items:
    +                    if out:
    +                        out.append(NL)
    +                    out.append( "%s%s- %s: " % (indent,('  '*depth), k) )
    +                    if isinstance(v,ParseResults):
    +                        if v:
    +                            out.append( v.dump(indent,depth+1) )
    +                        else:
    +                            out.append(_ustr(v))
    +                    else:
    +                        out.append(repr(v))
    +            elif any(isinstance(vv,ParseResults) for vv in self):
    +                v = self
    +                for i,vv in enumerate(v):
    +                    if isinstance(vv,ParseResults):
    +                        out.append("\n%s%s[%d]:\n%s%s%s" % (indent,('  '*(depth)),i,indent,('  '*(depth+1)),vv.dump(indent,depth+1) ))
    +                    else:
    +                        out.append("\n%s%s[%d]:\n%s%s%s" % (indent,('  '*(depth)),i,indent,('  '*(depth+1)),_ustr(vv)))
    +            
    +        return "".join(out)
    +
    +    def pprint(self, *args, **kwargs):
    +        """
    +        Pretty-printer for parsed results as a list, using the C{pprint} module.
    +        Accepts additional positional or keyword args as defined for the 
    +        C{pprint.pprint} method. (U{http://docs.python.org/3/library/pprint.html#pprint.pprint})
    +
    +        Example::
    +            ident = Word(alphas, alphanums)
    +            num = Word(nums)
    +            func = Forward()
    +            term = ident | num | Group('(' + func + ')')
    +            func <<= ident + Group(Optional(delimitedList(term)))
    +            result = func.parseString("fna a,b,(fnb c,d,200),100")
    +            result.pprint(width=40)
    +        prints::
    +            ['fna',
    +             ['a',
    +              'b',
    +              ['(', 'fnb', ['c', 'd', '200'], ')'],
    +              '100']]
    +        """
    +        pprint.pprint(self.asList(), *args, **kwargs)
    +
    +    # add support for pickle protocol
    +    def __getstate__(self):
    +        return ( self.__toklist,
    +                 ( self.__tokdict.copy(),
    +                   self.__parent is not None and self.__parent() or None,
    +                   self.__accumNames,
    +                   self.__name ) )
    +
    +    def __setstate__(self,state):
    +        self.__toklist = state[0]
    +        (self.__tokdict,
    +         par,
    +         inAccumNames,
    +         self.__name) = state[1]
    +        self.__accumNames = {}
    +        self.__accumNames.update(inAccumNames)
    +        if par is not None:
    +            self.__parent = wkref(par)
    +        else:
    +            self.__parent = None
    +
    +    def __getnewargs__(self):
    +        return self.__toklist, self.__name, self.__asList, self.__modal
    +
    +    def __dir__(self):
    +        return (dir(type(self)) + list(self.keys()))
    +
    +MutableMapping.register(ParseResults)
    +
    +def col (loc,strg):
    +    """Returns current column within a string, counting newlines as line separators.
    +   The first column is number 1.
    +
    +   Note: the default parsing behavior is to expand tabs in the input string
    +   before starting the parsing process.  See L{I{ParserElement.parseString}} for more information
    +   on parsing strings containing C{}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.
    +   """
    +    s = strg
    +    return 1 if 0} for more information
    +   on parsing strings containing C{}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.
    +   """
    +    return strg.count("\n",0,loc) + 1
    +
    +def line( loc, strg ):
    +    """Returns the line of text containing loc within a string, counting newlines as line separators.
    +       """
    +    lastCR = strg.rfind("\n", 0, loc)
    +    nextCR = strg.find("\n", loc)
    +    if nextCR >= 0:
    +        return strg[lastCR+1:nextCR]
    +    else:
    +        return strg[lastCR+1:]
    +
    +def _defaultStartDebugAction( instring, loc, expr ):
    +    print (("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )))
    +
    +def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ):
    +    print ("Matched " + _ustr(expr) + " -> " + str(toks.asList()))
    +
    +def _defaultExceptionDebugAction( instring, loc, expr, exc ):
    +    print ("Exception raised:" + _ustr(exc))
    +
    +def nullDebugAction(*args):
    +    """'Do-nothing' debug action, to suppress debugging output during parsing."""
    +    pass
    +
    +# Only works on Python 3.x - nonlocal is toxic to Python 2 installs
    +#~ 'decorator to trim function calls to match the arity of the target'
    +#~ def _trim_arity(func, maxargs=3):
    +    #~ if func in singleArgBuiltins:
    +        #~ return lambda s,l,t: func(t)
    +    #~ limit = 0
    +    #~ foundArity = False
    +    #~ def wrapper(*args):
    +        #~ nonlocal limit,foundArity
    +        #~ while 1:
    +            #~ try:
    +                #~ ret = func(*args[limit:])
    +                #~ foundArity = True
    +                #~ return ret
    +            #~ except TypeError:
    +                #~ if limit == maxargs or foundArity:
    +                    #~ raise
    +                #~ limit += 1
    +                #~ continue
    +    #~ return wrapper
    +
    +# this version is Python 2.x-3.x cross-compatible
    +'decorator to trim function calls to match the arity of the target'
    +def _trim_arity(func, maxargs=2):
    +    if func in singleArgBuiltins:
    +        return lambda s,l,t: func(t)
    +    limit = [0]
    +    foundArity = [False]
    +    
    +    # traceback return data structure changed in Py3.5 - normalize back to plain tuples
    +    if system_version[:2] >= (3,5):
    +        def extract_stack(limit=0):
    +            # special handling for Python 3.5.0 - extra deep call stack by 1
    +            offset = -3 if system_version == (3,5,0) else -2
    +            frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset]
    +            return [frame_summary[:2]]
    +        def extract_tb(tb, limit=0):
    +            frames = traceback.extract_tb(tb, limit=limit)
    +            frame_summary = frames[-1]
    +            return [frame_summary[:2]]
    +    else:
    +        extract_stack = traceback.extract_stack
    +        extract_tb = traceback.extract_tb
    +    
    +    # synthesize what would be returned by traceback.extract_stack at the call to 
    +    # user's parse action 'func', so that we don't incur call penalty at parse time
    +    
    +    LINE_DIFF = 6
    +    # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND 
    +    # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!!
    +    this_line = extract_stack(limit=2)[-1]
    +    pa_call_line_synth = (this_line[0], this_line[1]+LINE_DIFF)
    +
    +    def wrapper(*args):
    +        while 1:
    +            try:
    +                ret = func(*args[limit[0]:])
    +                foundArity[0] = True
    +                return ret
    +            except TypeError:
    +                # re-raise TypeErrors if they did not come from our arity testing
    +                if foundArity[0]:
    +                    raise
    +                else:
    +                    try:
    +                        tb = sys.exc_info()[-1]
    +                        if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth:
    +                            raise
    +                    finally:
    +                        del tb
    +
    +                if limit[0] <= maxargs:
    +                    limit[0] += 1
    +                    continue
    +                raise
    +
    +    # copy func name to wrapper for sensible debug output
    +    func_name = ""
    +    try:
    +        func_name = getattr(func, '__name__', 
    +                            getattr(func, '__class__').__name__)
    +    except Exception:
    +        func_name = str(func)
    +    wrapper.__name__ = func_name
    +
    +    return wrapper
    +
    +class ParserElement(object):
    +    """Abstract base level parser element class."""
    +    DEFAULT_WHITE_CHARS = " \n\t\r"
    +    verbose_stacktrace = False
    +
    +    @staticmethod
    +    def setDefaultWhitespaceChars( chars ):
    +        r"""
    +        Overrides the default whitespace chars
    +
    +        Example::
    +            # default whitespace chars are space,  and newline
    +            OneOrMore(Word(alphas)).parseString("abc def\nghi jkl")  # -> ['abc', 'def', 'ghi', 'jkl']
    +            
    +            # change to just treat newline as significant
    +            ParserElement.setDefaultWhitespaceChars(" \t")
    +            OneOrMore(Word(alphas)).parseString("abc def\nghi jkl")  # -> ['abc', 'def']
    +        """
    +        ParserElement.DEFAULT_WHITE_CHARS = chars
    +
    +    @staticmethod
    +    def inlineLiteralsUsing(cls):
    +        """
    +        Set class to be used for inclusion of string literals into a parser.
    +        
    +        Example::
    +            # default literal class used is Literal
    +            integer = Word(nums)
    +            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
    +
    +            date_str.parseString("1999/12/31")  # -> ['1999', '/', '12', '/', '31']
    +
    +
    +            # change to Suppress
    +            ParserElement.inlineLiteralsUsing(Suppress)
    +            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
    +
    +            date_str.parseString("1999/12/31")  # -> ['1999', '12', '31']
    +        """
    +        ParserElement._literalStringClass = cls
    +
    +    def __init__( self, savelist=False ):
    +        self.parseAction = list()
    +        self.failAction = None
    +        #~ self.name = ""  # don't define self.name, let subclasses try/except upcall
    +        self.strRepr = None
    +        self.resultsName = None
    +        self.saveAsList = savelist
    +        self.skipWhitespace = True
    +        self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
    +        self.copyDefaultWhiteChars = True
    +        self.mayReturnEmpty = False # used when checking for left-recursion
    +        self.keepTabs = False
    +        self.ignoreExprs = list()
    +        self.debug = False
    +        self.streamlined = False
    +        self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index
    +        self.errmsg = ""
    +        self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all)
    +        self.debugActions = ( None, None, None ) #custom debug actions
    +        self.re = None
    +        self.callPreparse = True # used to avoid redundant calls to preParse
    +        self.callDuringTry = False
    +
    +    def copy( self ):
    +        """
    +        Make a copy of this C{ParserElement}.  Useful for defining different parse actions
    +        for the same parsing pattern, using copies of the original parse element.
    +        
    +        Example::
    +            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
    +            integerK = integer.copy().addParseAction(lambda toks: toks[0]*1024) + Suppress("K")
    +            integerM = integer.copy().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
    +            
    +            print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M"))
    +        prints::
    +            [5120, 100, 655360, 268435456]
    +        Equivalent form of C{expr.copy()} is just C{expr()}::
    +            integerM = integer().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
    +        """
    +        cpy = copy.copy( self )
    +        cpy.parseAction = self.parseAction[:]
    +        cpy.ignoreExprs = self.ignoreExprs[:]
    +        if self.copyDefaultWhiteChars:
    +            cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
    +        return cpy
    +
    +    def setName( self, name ):
    +        """
    +        Define name for this expression, makes debugging and exception messages clearer.
    +        
    +        Example::
    +            Word(nums).parseString("ABC")  # -> Exception: Expected W:(0123...) (at char 0), (line:1, col:1)
    +            Word(nums).setName("integer").parseString("ABC")  # -> Exception: Expected integer (at char 0), (line:1, col:1)
    +        """
    +        self.name = name
    +        self.errmsg = "Expected " + self.name
    +        if hasattr(self,"exception"):
    +            self.exception.msg = self.errmsg
    +        return self
    +
    +    def setResultsName( self, name, listAllMatches=False ):
    +        """
    +        Define name for referencing matching tokens as a nested attribute
    +        of the returned parse results.
    +        NOTE: this returns a *copy* of the original C{ParserElement} object;
    +        this is so that the client can define a basic element, such as an
    +        integer, and reference it in multiple places with different names.
    +
    +        You can also set results names using the abbreviated syntax,
    +        C{expr("name")} in place of C{expr.setResultsName("name")} - 
    +        see L{I{__call__}<__call__>}.
    +
    +        Example::
    +            date_str = (integer.setResultsName("year") + '/' 
    +                        + integer.setResultsName("month") + '/' 
    +                        + integer.setResultsName("day"))
    +
    +            # equivalent form:
    +            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
    +        """
    +        newself = self.copy()
    +        if name.endswith("*"):
    +            name = name[:-1]
    +            listAllMatches=True
    +        newself.resultsName = name
    +        newself.modalResults = not listAllMatches
    +        return newself
    +
    +    def setBreak(self,breakFlag = True):
    +        """Method to invoke the Python pdb debugger when this element is
    +           about to be parsed. Set C{breakFlag} to True to enable, False to
    +           disable.
    +        """
    +        if breakFlag:
    +            _parseMethod = self._parse
    +            def breaker(instring, loc, doActions=True, callPreParse=True):
    +                import pdb
    +                pdb.set_trace()
    +                return _parseMethod( instring, loc, doActions, callPreParse )
    +            breaker._originalParseMethod = _parseMethod
    +            self._parse = breaker
    +        else:
    +            if hasattr(self._parse,"_originalParseMethod"):
    +                self._parse = self._parse._originalParseMethod
    +        return self
    +
    +    def setParseAction( self, *fns, **kwargs ):
    +        """
    +        Define one or more actions to perform when successfully matching parse element definition.
    +        Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)},
    +        C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where:
    +         - s   = the original string being parsed (see note below)
    +         - loc = the location of the matching substring
    +         - toks = a list of the matched tokens, packaged as a C{L{ParseResults}} object
    +        If the functions in fns modify the tokens, they can return them as the return
    +        value from fn, and the modified list of tokens will replace the original.
    +        Otherwise, fn does not need to return any value.
    +
    +        Optional keyword arguments:
    +         - callDuringTry = (default=C{False}) indicate if parse action should be run during lookaheads and alternate testing
    +
    +        Note: the default parsing behavior is to expand tabs in the input string
    +        before starting the parsing process.  See L{I{parseString}} for more information
    +        on parsing strings containing C{}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::
    +            integer = Word(nums)
    +            date_str = integer + '/' + integer + '/' + integer
    +
    +            date_str.parseString("1999/12/31")  # -> ['1999', '/', '12', '/', '31']
    +
    +            # use parse action to convert to ints at parse time
    +            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
    +            date_str = integer + '/' + integer + '/' + integer
    +
    +            # note that integer fields are now ints, not strings
    +            date_str.parseString("1999/12/31")  # -> [1999, '/', 12, '/', 31]
    +        """
    +        self.parseAction = list(map(_trim_arity, list(fns)))
    +        self.callDuringTry = kwargs.get("callDuringTry", False)
    +        return self
    +
    +    def addParseAction( self, *fns, **kwargs ):
    +        """
    +        Add one or more parse actions to expression's list of parse actions. See L{I{setParseAction}}.
    +        
    +        See examples in L{I{copy}}.
    +        """
    +        self.parseAction += list(map(_trim_arity, list(fns)))
    +        self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
    +        return self
    +
    +    def addCondition(self, *fns, **kwargs):
    +        """Add a boolean predicate function to expression's list of parse actions. See 
    +        L{I{setParseAction}} for function call signatures. Unlike C{setParseAction}, 
    +        functions passed to C{addCondition} need to return boolean success/fail of the condition.
    +
    +        Optional keyword arguments:
    +         - message = define a custom message to be used in the raised exception
    +         - fatal   = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException
    +         
    +        Example::
    +            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
    +            year_int = integer.copy()
    +            year_int.addCondition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later")
    +            date_str = year_int + '/' + integer + '/' + integer
    +
    +            result = date_str.parseString("1999/12/31")  # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1)
    +        """
    +        msg = kwargs.get("message", "failed user-defined condition")
    +        exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException
    +        for fn in fns:
    +            def pa(s,l,t):
    +                if not bool(_trim_arity(fn)(s,l,t)):
    +                    raise exc_type(s,l,msg)
    +            self.parseAction.append(pa)
    +        self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
    +        return self
    +
    +    def setFailAction( self, fn ):
    +        """Define action to perform if parsing fails at this expression.
    +           Fail acton fn is a callable function that takes the arguments
    +           C{fn(s,loc,expr,err)} where:
    +            - s = string being parsed
    +            - loc = location where expression match was attempted and failed
    +            - expr = the parse expression that failed
    +            - err = the exception thrown
    +           The function returns no value.  It may throw C{L{ParseFatalException}}
    +           if it is desired to stop parsing immediately."""
    +        self.failAction = fn
    +        return self
    +
    +    def _skipIgnorables( self, instring, loc ):
    +        exprsFound = True
    +        while exprsFound:
    +            exprsFound = False
    +            for e in self.ignoreExprs:
    +                try:
    +                    while 1:
    +                        loc,dummy = e._parse( instring, loc )
    +                        exprsFound = True
    +                except ParseException:
    +                    pass
    +        return loc
    +
    +    def preParse( self, instring, loc ):
    +        if self.ignoreExprs:
    +            loc = self._skipIgnorables( instring, loc )
    +
    +        if self.skipWhitespace:
    +            wt = self.whiteChars
    +            instrlen = len(instring)
    +            while loc < instrlen and instring[loc] in wt:
    +                loc += 1
    +
    +        return loc
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        return loc, []
    +
    +    def postParse( self, instring, loc, tokenlist ):
    +        return tokenlist
    +
    +    #~ @profile
    +    def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ):
    +        debugging = ( self.debug ) #and doActions )
    +
    +        if debugging or self.failAction:
    +            #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) ))
    +            if (self.debugActions[0] ):
    +                self.debugActions[0]( instring, loc, self )
    +            if callPreParse and self.callPreparse:
    +                preloc = self.preParse( instring, loc )
    +            else:
    +                preloc = loc
    +            tokensStart = preloc
    +            try:
    +                try:
    +                    loc,tokens = self.parseImpl( instring, preloc, doActions )
    +                except IndexError:
    +                    raise ParseException( instring, len(instring), self.errmsg, self )
    +            except ParseBaseException as err:
    +                #~ print ("Exception raised:", err)
    +                if self.debugActions[2]:
    +                    self.debugActions[2]( instring, tokensStart, self, err )
    +                if self.failAction:
    +                    self.failAction( instring, tokensStart, self, err )
    +                raise
    +        else:
    +            if callPreParse and self.callPreparse:
    +                preloc = self.preParse( instring, loc )
    +            else:
    +                preloc = loc
    +            tokensStart = preloc
    +            if self.mayIndexError or preloc >= len(instring):
    +                try:
    +                    loc,tokens = self.parseImpl( instring, preloc, doActions )
    +                except IndexError:
    +                    raise ParseException( instring, len(instring), self.errmsg, self )
    +            else:
    +                loc,tokens = self.parseImpl( instring, preloc, doActions )
    +
    +        tokens = self.postParse( instring, loc, tokens )
    +
    +        retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults )
    +        if self.parseAction and (doActions or self.callDuringTry):
    +            if debugging:
    +                try:
    +                    for fn in self.parseAction:
    +                        tokens = fn( instring, tokensStart, retTokens )
    +                        if tokens is not None:
    +                            retTokens = ParseResults( tokens,
    +                                                      self.resultsName,
    +                                                      asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
    +                                                      modal=self.modalResults )
    +                except ParseBaseException as err:
    +                    #~ print "Exception raised in user parse action:", err
    +                    if (self.debugActions[2] ):
    +                        self.debugActions[2]( instring, tokensStart, self, err )
    +                    raise
    +            else:
    +                for fn in self.parseAction:
    +                    tokens = fn( instring, tokensStart, retTokens )
    +                    if tokens is not None:
    +                        retTokens = ParseResults( tokens,
    +                                                  self.resultsName,
    +                                                  asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
    +                                                  modal=self.modalResults )
    +        if debugging:
    +            #~ print ("Matched",self,"->",retTokens.asList())
    +            if (self.debugActions[1] ):
    +                self.debugActions[1]( instring, tokensStart, loc, self, retTokens )
    +
    +        return loc, retTokens
    +
    +    def tryParse( self, instring, loc ):
    +        try:
    +            return self._parse( instring, loc, doActions=False )[0]
    +        except ParseFatalException:
    +            raise ParseException( instring, loc, self.errmsg, self)
    +    
    +    def canParseNext(self, instring, loc):
    +        try:
    +            self.tryParse(instring, loc)
    +        except (ParseException, IndexError):
    +            return False
    +        else:
    +            return True
    +
    +    class _UnboundedCache(object):
    +        def __init__(self):
    +            cache = {}
    +            self.not_in_cache = not_in_cache = object()
    +
    +            def get(self, key):
    +                return cache.get(key, not_in_cache)
    +
    +            def set(self, key, value):
    +                cache[key] = value
    +
    +            def clear(self):
    +                cache.clear()
    +                
    +            def cache_len(self):
    +                return len(cache)
    +
    +            self.get = types.MethodType(get, self)
    +            self.set = types.MethodType(set, self)
    +            self.clear = types.MethodType(clear, self)
    +            self.__len__ = types.MethodType(cache_len, self)
    +
    +    if _OrderedDict is not None:
    +        class _FifoCache(object):
    +            def __init__(self, size):
    +                self.not_in_cache = not_in_cache = object()
    +
    +                cache = _OrderedDict()
    +
    +                def get(self, key):
    +                    return cache.get(key, not_in_cache)
    +
    +                def set(self, key, value):
    +                    cache[key] = value
    +                    while len(cache) > size:
    +                        try:
    +                            cache.popitem(False)
    +                        except KeyError:
    +                            pass
    +
    +                def clear(self):
    +                    cache.clear()
    +
    +                def cache_len(self):
    +                    return len(cache)
    +
    +                self.get = types.MethodType(get, self)
    +                self.set = types.MethodType(set, self)
    +                self.clear = types.MethodType(clear, self)
    +                self.__len__ = types.MethodType(cache_len, self)
    +
    +    else:
    +        class _FifoCache(object):
    +            def __init__(self, size):
    +                self.not_in_cache = not_in_cache = object()
    +
    +                cache = {}
    +                key_fifo = collections.deque([], size)
    +
    +                def get(self, key):
    +                    return cache.get(key, not_in_cache)
    +
    +                def set(self, key, value):
    +                    cache[key] = value
    +                    while len(key_fifo) > size:
    +                        cache.pop(key_fifo.popleft(), None)
    +                    key_fifo.append(key)
    +
    +                def clear(self):
    +                    cache.clear()
    +                    key_fifo.clear()
    +
    +                def cache_len(self):
    +                    return len(cache)
    +
    +                self.get = types.MethodType(get, self)
    +                self.set = types.MethodType(set, self)
    +                self.clear = types.MethodType(clear, self)
    +                self.__len__ = types.MethodType(cache_len, self)
    +
    +    # argument cache for optimizing repeated calls when backtracking through recursive expressions
    +    packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail
    +    packrat_cache_lock = RLock()
    +    packrat_cache_stats = [0, 0]
    +
    +    # this method gets repeatedly called during backtracking with the same arguments -
    +    # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression
    +    def _parseCache( self, instring, loc, doActions=True, callPreParse=True ):
    +        HIT, MISS = 0, 1
    +        lookup = (self, instring, loc, callPreParse, doActions)
    +        with ParserElement.packrat_cache_lock:
    +            cache = ParserElement.packrat_cache
    +            value = cache.get(lookup)
    +            if value is cache.not_in_cache:
    +                ParserElement.packrat_cache_stats[MISS] += 1
    +                try:
    +                    value = self._parseNoCache(instring, loc, doActions, callPreParse)
    +                except ParseBaseException as pe:
    +                    # cache a copy of the exception, without the traceback
    +                    cache.set(lookup, pe.__class__(*pe.args))
    +                    raise
    +                else:
    +                    cache.set(lookup, (value[0], value[1].copy()))
    +                    return value
    +            else:
    +                ParserElement.packrat_cache_stats[HIT] += 1
    +                if isinstance(value, Exception):
    +                    raise value
    +                return (value[0], value[1].copy())
    +
    +    _parse = _parseNoCache
    +
    +    @staticmethod
    +    def resetCache():
    +        ParserElement.packrat_cache.clear()
    +        ParserElement.packrat_cache_stats[:] = [0] * len(ParserElement.packrat_cache_stats)
    +
    +    _packratEnabled = False
    +    @staticmethod
    +    def enablePackrat(cache_size_limit=128):
    +        """Enables "packrat" parsing, which adds memoizing to the parsing logic.
    +           Repeated parse attempts at the same string location (which happens
    +           often in many complex grammars) can immediately return a cached value,
    +           instead of re-executing parsing/validating code.  Memoizing is done of
    +           both valid results and parsing exceptions.
    +           
    +           Parameters:
    +            - cache_size_limit - (default=C{128}) - if an integer value is provided
    +              will limit the size of the packrat cache; if None is passed, then
    +              the cache size will be unbounded; if 0 is passed, the cache will
    +              be effectively disabled.
    +            
    +           This speedup may break existing programs that use parse actions that
    +           have side-effects.  For this reason, packrat parsing is disabled when
    +           you first import pyparsing.  To activate the packrat feature, your
    +           program must call the class method C{ParserElement.enablePackrat()}.  If
    +           your program uses C{psyco} to "compile as you go", you must call
    +           C{enablePackrat} before calling C{psyco.full()}.  If you do not do this,
    +           Python will crash.  For best results, call C{enablePackrat()} immediately
    +           after importing pyparsing.
    +           
    +           Example::
    +               import pyparsing
    +               pyparsing.ParserElement.enablePackrat()
    +        """
    +        if not ParserElement._packratEnabled:
    +            ParserElement._packratEnabled = True
    +            if cache_size_limit is None:
    +                ParserElement.packrat_cache = ParserElement._UnboundedCache()
    +            else:
    +                ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit)
    +            ParserElement._parse = ParserElement._parseCache
    +
    +    def parseString( self, instring, parseAll=False ):
    +        """
    +        Execute the parse expression with the given string.
    +        This is the main interface to the client code, once the complete
    +        expression has been built.
    +
    +        If you want the grammar to require that the entire input string be
    +        successfully parsed, then set C{parseAll} to True (equivalent to ending
    +        the grammar with C{L{StringEnd()}}).
    +
    +        Note: C{parseString} implicitly calls C{expandtabs()} on the input string,
    +        in order to report proper column numbers in parse actions.
    +        If the input string contains tabs and
    +        the grammar uses parse actions that use the C{loc} argument to index into the
    +        string being parsed, you can ensure you have a consistent view of the input
    +        string by:
    +         - calling C{parseWithTabs} on your grammar before calling C{parseString}
    +           (see L{I{parseWithTabs}})
    +         - define your parse action using the full C{(s,loc,toks)} signature, and
    +           reference the input string using the parse action's C{s} argument
    +         - explictly expand the tabs in your input string before calling
    +           C{parseString}
    +        
    +        Example::
    +            Word('a').parseString('aaaaabaaa')  # -> ['aaaaa']
    +            Word('a').parseString('aaaaabaaa', parseAll=True)  # -> Exception: Expected end of text
    +        """
    +        ParserElement.resetCache()
    +        if not self.streamlined:
    +            self.streamline()
    +            #~ self.saveAsList = True
    +        for e in self.ignoreExprs:
    +            e.streamline()
    +        if not self.keepTabs:
    +            instring = instring.expandtabs()
    +        try:
    +            loc, tokens = self._parse( instring, 0 )
    +            if parseAll:
    +                loc = self.preParse( instring, loc )
    +                se = Empty() + StringEnd()
    +                se._parse( instring, loc )
    +        except ParseBaseException as exc:
    +            if ParserElement.verbose_stacktrace:
    +                raise
    +            else:
    +                # catch and re-raise exception from here, clears out pyparsing internal stack trace
    +                raise exc
    +        else:
    +            return tokens
    +
    +    def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ):
    +        """
    +        Scan the input string for expression matches.  Each match will return the
    +        matching tokens, start location, and end location.  May be called with optional
    +        C{maxMatches} argument, to clip scanning after 'n' matches are found.  If
    +        C{overlap} is specified, then overlapping matches will be reported.
    +
    +        Note that the start and end locations are reported relative to the string
    +        being parsed.  See L{I{parseString}} for more information on parsing
    +        strings with embedded tabs.
    +
    +        Example::
    +            source = "sldjf123lsdjjkf345sldkjf879lkjsfd987"
    +            print(source)
    +            for tokens,start,end in Word(alphas).scanString(source):
    +                print(' '*start + '^'*(end-start))
    +                print(' '*start + tokens[0])
    +        
    +        prints::
    +        
    +            sldjf123lsdjjkf345sldkjf879lkjsfd987
    +            ^^^^^
    +            sldjf
    +                    ^^^^^^^
    +                    lsdjjkf
    +                              ^^^^^^
    +                              sldkjf
    +                                       ^^^^^^
    +                                       lkjsfd
    +        """
    +        if not self.streamlined:
    +            self.streamline()
    +        for e in self.ignoreExprs:
    +            e.streamline()
    +
    +        if not self.keepTabs:
    +            instring = _ustr(instring).expandtabs()
    +        instrlen = len(instring)
    +        loc = 0
    +        preparseFn = self.preParse
    +        parseFn = self._parse
    +        ParserElement.resetCache()
    +        matches = 0
    +        try:
    +            while loc <= instrlen and matches < maxMatches:
    +                try:
    +                    preloc = preparseFn( instring, loc )
    +                    nextLoc,tokens = parseFn( instring, preloc, callPreParse=False )
    +                except ParseException:
    +                    loc = preloc+1
    +                else:
    +                    if nextLoc > loc:
    +                        matches += 1
    +                        yield tokens, preloc, nextLoc
    +                        if overlap:
    +                            nextloc = preparseFn( instring, loc )
    +                            if nextloc > loc:
    +                                loc = nextLoc
    +                            else:
    +                                loc += 1
    +                        else:
    +                            loc = nextLoc
    +                    else:
    +                        loc = preloc+1
    +        except ParseBaseException as exc:
    +            if ParserElement.verbose_stacktrace:
    +                raise
    +            else:
    +                # catch and re-raise exception from here, clears out pyparsing internal stack trace
    +                raise exc
    +
    +    def transformString( self, instring ):
    +        """
    +        Extension to C{L{scanString}}, to modify matching text with modified tokens that may
    +        be returned from a parse action.  To use C{transformString}, define a grammar and
    +        attach a parse action to it that modifies the returned token list.
    +        Invoking C{transformString()} on a target string will then scan for matches,
    +        and replace the matched text patterns according to the logic in the parse
    +        action.  C{transformString()} returns the resulting transformed string.
    +        
    +        Example::
    +            wd = Word(alphas)
    +            wd.setParseAction(lambda toks: toks[0].title())
    +            
    +            print(wd.transformString("now is the winter of our discontent made glorious summer by this sun of york."))
    +        Prints::
    +            Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York.
    +        """
    +        out = []
    +        lastE = 0
    +        # force preservation of s, to minimize unwanted transformation of string, and to
    +        # keep string locs straight between transformString and scanString
    +        self.keepTabs = True
    +        try:
    +            for t,s,e in self.scanString( instring ):
    +                out.append( instring[lastE:s] )
    +                if t:
    +                    if isinstance(t,ParseResults):
    +                        out += t.asList()
    +                    elif isinstance(t,list):
    +                        out += t
    +                    else:
    +                        out.append(t)
    +                lastE = e
    +            out.append(instring[lastE:])
    +            out = [o for o in out if o]
    +            return "".join(map(_ustr,_flatten(out)))
    +        except ParseBaseException as exc:
    +            if ParserElement.verbose_stacktrace:
    +                raise
    +            else:
    +                # catch and re-raise exception from here, clears out pyparsing internal stack trace
    +                raise exc
    +
    +    def searchString( self, instring, maxMatches=_MAX_INT ):
    +        """
    +        Another extension to C{L{scanString}}, simplifying the access to the tokens found
    +        to match the given parse expression.  May be called with optional
    +        C{maxMatches} argument, to clip searching after 'n' matches are found.
    +        
    +        Example::
    +            # 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.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))
    +
    +            # the sum() builtin can be used to merge results into a single ParseResults object
    +            print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")))
    +        prints::
    +            [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']]
    +            ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity']
    +        """
    +        try:
    +            return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ])
    +        except ParseBaseException as exc:
    +            if ParserElement.verbose_stacktrace:
    +                raise
    +            else:
    +                # catch and re-raise exception from here, clears out pyparsing internal stack trace
    +                raise exc
    +
    +    def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False):
    +        """
    +        Generator method to split a string using the given expression as a separator.
    +        May be called with optional C{maxsplit} argument, to limit the number of splits;
    +        and the optional C{includeSeparators} argument (default=C{False}), if the separating
    +        matching text should be included in the split results.
    +        
    +        Example::        
    +            punc = oneOf(list(".,;:/-!?"))
    +            print(list(punc.split("This, this?, this sentence, is badly punctuated!")))
    +        prints::
    +            ['This', ' this', '', ' this sentence', ' is badly punctuated', '']
    +        """
    +        splits = 0
    +        last = 0
    +        for t,s,e in self.scanString(instring, maxMatches=maxsplit):
    +            yield instring[last:s]
    +            if includeSeparators:
    +                yield t[0]
    +            last = e
    +        yield instring[last:]
    +
    +    def __add__(self, other ):
    +        """
    +        Implementation of + operator - returns C{L{And}}. Adding strings to a ParserElement
    +        converts them to L{Literal}s by default.
    +        
    +        Example::
    +            greet = Word(alphas) + "," + Word(alphas) + "!"
    +            hello = "Hello, World!"
    +            print (hello, "->", greet.parseString(hello))
    +        Prints::
    +            Hello, World! -> ['Hello', ',', 'World', '!']
    +        """
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass( other )
    +        if not isinstance( other, ParserElement ):
    +            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
    +                    SyntaxWarning, stacklevel=2)
    +            return None
    +        return And( [ self, other ] )
    +
    +    def __radd__(self, other ):
    +        """
    +        Implementation of + operator when left operand is not a C{L{ParserElement}}
    +        """
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass( other )
    +        if not isinstance( other, ParserElement ):
    +            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
    +                    SyntaxWarning, stacklevel=2)
    +            return None
    +        return other + self
    +
    +    def __sub__(self, other):
    +        """
    +        Implementation of - operator, returns C{L{And}} with error stop
    +        """
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass( other )
    +        if not isinstance( other, ParserElement ):
    +            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
    +                    SyntaxWarning, stacklevel=2)
    +            return None
    +        return self + And._ErrorStop() + other
    +
    +    def __rsub__(self, other ):
    +        """
    +        Implementation of - operator when left operand is not a C{L{ParserElement}}
    +        """
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass( other )
    +        if not isinstance( other, ParserElement ):
    +            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
    +                    SyntaxWarning, stacklevel=2)
    +            return None
    +        return other - self
    +
    +    def __mul__(self,other):
    +        """
    +        Implementation of * operator, allows use of C{expr * 3} in place of
    +        C{expr + expr + expr}.  Expressions may also me multiplied by a 2-integer
    +        tuple, similar to C{{min,max}} multipliers in regular expressions.  Tuples
    +        may also include C{None} as in:
    +         - C{expr*(n,None)} or C{expr*(n,)} is equivalent
    +              to C{expr*n + L{ZeroOrMore}(expr)}
    +              (read as "at least n instances of C{expr}")
    +         - C{expr*(None,n)} is equivalent to C{expr*(0,n)}
    +              (read as "0 to n instances of C{expr}")
    +         - C{expr*(None,None)} is equivalent to C{L{ZeroOrMore}(expr)}
    +         - C{expr*(1,None)} is equivalent to C{L{OneOrMore}(expr)}
    +
    +        Note that C{expr*(None,n)} does not raise an exception if
    +        more than n exprs exist in the input stream; that is,
    +        C{expr*(None,n)} does not enforce a maximum number of expr
    +        occurrences.  If this behavior is desired, then write
    +        C{expr*(None,n) + ~expr}
    +        """
    +        if isinstance(other,int):
    +            minElements, optElements = other,0
    +        elif isinstance(other,tuple):
    +            other = (other + (None, None))[:2]
    +            if other[0] is None:
    +                other = (0, other[1])
    +            if isinstance(other[0],int) and other[1] is None:
    +                if other[0] == 0:
    +                    return ZeroOrMore(self)
    +                if other[0] == 1:
    +                    return OneOrMore(self)
    +                else:
    +                    return self*other[0] + ZeroOrMore(self)
    +            elif isinstance(other[0],int) and isinstance(other[1],int):
    +                minElements, optElements = other
    +                optElements -= minElements
    +            else:
    +                raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1]))
    +        else:
    +            raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other))
    +
    +        if minElements < 0:
    +            raise ValueError("cannot multiply ParserElement by negative value")
    +        if optElements < 0:
    +            raise ValueError("second tuple value must be greater or equal to first tuple value")
    +        if minElements == optElements == 0:
    +            raise ValueError("cannot multiply ParserElement by 0 or (0,0)")
    +
    +        if (optElements):
    +            def makeOptionalList(n):
    +                if n>1:
    +                    return Optional(self + makeOptionalList(n-1))
    +                else:
    +                    return Optional(self)
    +            if minElements:
    +                if minElements == 1:
    +                    ret = self + makeOptionalList(optElements)
    +                else:
    +                    ret = And([self]*minElements) + makeOptionalList(optElements)
    +            else:
    +                ret = makeOptionalList(optElements)
    +        else:
    +            if minElements == 1:
    +                ret = self
    +            else:
    +                ret = And([self]*minElements)
    +        return ret
    +
    +    def __rmul__(self, other):
    +        return self.__mul__(other)
    +
    +    def __or__(self, other ):
    +        """
    +        Implementation of | operator - returns C{L{MatchFirst}}
    +        """
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass( other )
    +        if not isinstance( other, ParserElement ):
    +            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
    +                    SyntaxWarning, stacklevel=2)
    +            return None
    +        return MatchFirst( [ self, other ] )
    +
    +    def __ror__(self, other ):
    +        """
    +        Implementation of | operator when left operand is not a C{L{ParserElement}}
    +        """
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass( other )
    +        if not isinstance( other, ParserElement ):
    +            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
    +                    SyntaxWarning, stacklevel=2)
    +            return None
    +        return other | self
    +
    +    def __xor__(self, other ):
    +        """
    +        Implementation of ^ operator - returns C{L{Or}}
    +        """
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass( other )
    +        if not isinstance( other, ParserElement ):
    +            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
    +                    SyntaxWarning, stacklevel=2)
    +            return None
    +        return Or( [ self, other ] )
    +
    +    def __rxor__(self, other ):
    +        """
    +        Implementation of ^ operator when left operand is not a C{L{ParserElement}}
    +        """
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass( other )
    +        if not isinstance( other, ParserElement ):
    +            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
    +                    SyntaxWarning, stacklevel=2)
    +            return None
    +        return other ^ self
    +
    +    def __and__(self, other ):
    +        """
    +        Implementation of & operator - returns C{L{Each}}
    +        """
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass( other )
    +        if not isinstance( other, ParserElement ):
    +            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
    +                    SyntaxWarning, stacklevel=2)
    +            return None
    +        return Each( [ self, other ] )
    +
    +    def __rand__(self, other ):
    +        """
    +        Implementation of & operator when left operand is not a C{L{ParserElement}}
    +        """
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass( other )
    +        if not isinstance( other, ParserElement ):
    +            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
    +                    SyntaxWarning, stacklevel=2)
    +            return None
    +        return other & self
    +
    +    def __invert__( self ):
    +        """
    +        Implementation of ~ operator - returns C{L{NotAny}}
    +        """
    +        return NotAny( self )
    +
    +    def __call__(self, name=None):
    +        """
    +        Shortcut for C{L{setResultsName}}, with C{listAllMatches=False}.
    +        
    +        If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be
    +        passed as C{True}.
    +           
    +        If C{name} is omitted, same as calling C{L{copy}}.
    +
    +        Example::
    +            # these are equivalent
    +            userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno")
    +            userdata = Word(alphas)("name") + Word(nums+"-")("socsecno")             
    +        """
    +        if name is not None:
    +            return self.setResultsName(name)
    +        else:
    +            return self.copy()
    +
    +    def suppress( self ):
    +        """
    +        Suppresses the output of this C{ParserElement}; useful to keep punctuation from
    +        cluttering up returned output.
    +        """
    +        return Suppress( self )
    +
    +    def leaveWhitespace( self ):
    +        """
    +        Disables the skipping of whitespace before matching the characters in the
    +        C{ParserElement}'s defined pattern.  This is normally only used internally by
    +        the pyparsing module, but may be needed in some whitespace-sensitive grammars.
    +        """
    +        self.skipWhitespace = False
    +        return self
    +
    +    def setWhitespaceChars( self, chars ):
    +        """
    +        Overrides the default whitespace chars
    +        """
    +        self.skipWhitespace = True
    +        self.whiteChars = chars
    +        self.copyDefaultWhiteChars = False
    +        return self
    +
    +    def parseWithTabs( self ):
    +        """
    +        Overrides default behavior to expand C{}s to spaces before parsing the input string.
    +        Must be called before C{parseString} when the input grammar contains elements that
    +        match C{} characters.
    +        """
    +        self.keepTabs = True
    +        return self
    +
    +    def ignore( self, other ):
    +        """
    +        Define expression to be ignored (e.g., comments) while doing pattern
    +        matching; may be called repeatedly, to define multiple comment or other
    +        ignorable patterns.
    +        
    +        Example::
    +            patt = OneOrMore(Word(alphas))
    +            patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj']
    +            
    +            patt.ignore(cStyleComment)
    +            patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj', 'lskjd']
    +        """
    +        if isinstance(other, basestring):
    +            other = Suppress(other)
    +
    +        if isinstance( other, Suppress ):
    +            if other not in self.ignoreExprs:
    +                self.ignoreExprs.append(other)
    +        else:
    +            self.ignoreExprs.append( Suppress( other.copy() ) )
    +        return self
    +
    +    def setDebugActions( self, startAction, successAction, exceptionAction ):
    +        """
    +        Enable display of debugging messages while doing pattern matching.
    +        """
    +        self.debugActions = (startAction or _defaultStartDebugAction,
    +                             successAction or _defaultSuccessDebugAction,
    +                             exceptionAction or _defaultExceptionDebugAction)
    +        self.debug = True
    +        return self
    +
    +    def setDebug( self, flag=True ):
    +        """
    +        Enable display of debugging messages while doing pattern matching.
    +        Set C{flag} to True to enable, False to disable.
    +
    +        Example::
    +            wd = Word(alphas).setName("alphaword")
    +            integer = Word(nums).setName("numword")
    +            term = wd | integer
    +            
    +            # turn on debugging for wd
    +            wd.setDebug()
    +
    +            OneOrMore(term).parseString("abc 123 xyz 890")
    +        
    +        prints::
    +            Match alphaword at loc 0(1,1)
    +            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)
    +            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)
    +
    +        The output shown is that produced by the default debug actions - custom debug actions can be
    +        specified using L{setDebugActions}. Prior to attempting
    +        to match the C{wd} expression, the debugging message C{"Match  at loc (,)"}
    +        is shown. Then if the parse succeeds, a C{"Matched"} message is shown, or an C{"Exception raised"}
    +        message is shown. Also note the use of L{setName} 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 C{Word} expression without calling C{setName} is C{"W:(ABCD...)"}.
    +        """
    +        if flag:
    +            self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction )
    +        else:
    +            self.debug = False
    +        return self
    +
    +    def __str__( self ):
    +        return self.name
    +
    +    def __repr__( self ):
    +        return _ustr(self)
    +
    +    def streamline( self ):
    +        self.streamlined = True
    +        self.strRepr = None
    +        return self
    +
    +    def checkRecursion( self, parseElementList ):
    +        pass
    +
    +    def validate( self, validateTrace=[] ):
    +        """
    +        Check defined expressions for valid structure, check for infinite recursive definitions.
    +        """
    +        self.checkRecursion( [] )
    +
    +    def parseFile( self, file_or_filename, parseAll=False ):
    +        """
    +        Execute the parse expression on the given file or filename.
    +        If a filename is specified (instead of a file object),
    +        the entire file is opened, read, and closed before parsing.
    +        """
    +        try:
    +            file_contents = file_or_filename.read()
    +        except AttributeError:
    +            with open(file_or_filename, "r") as f:
    +                file_contents = f.read()
    +        try:
    +            return self.parseString(file_contents, parseAll)
    +        except ParseBaseException as exc:
    +            if ParserElement.verbose_stacktrace:
    +                raise
    +            else:
    +                # catch and re-raise exception from here, clears out pyparsing internal stack trace
    +                raise exc
    +
    +    def __eq__(self,other):
    +        if isinstance(other, ParserElement):
    +            return self is other or vars(self) == vars(other)
    +        elif isinstance(other, basestring):
    +            return self.matches(other)
    +        else:
    +            return super(ParserElement,self)==other
    +
    +    def __ne__(self,other):
    +        return not (self == other)
    +
    +    def __hash__(self):
    +        return hash(id(self))
    +
    +    def __req__(self,other):
    +        return self == other
    +
    +    def __rne__(self,other):
    +        return not (self == other)
    +
    +    def matches(self, testString, parseAll=True):
    +        """
    +        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:
    +         - testString - to test against this expression for a match
    +         - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests
    +            
    +        Example::
    +            expr = Word(nums)
    +            assert expr.matches("100")
    +        """
    +        try:
    +            self.parseString(_ustr(testString), parseAll=parseAll)
    +            return True
    +        except ParseBaseException:
    +            return False
    +                
    +    def runTests(self, tests, parseAll=True, comment='#', fullDump=True, printResults=True, failureTests=False):
    +        """
    +        Execute the parse expression on a series of test strings, showing each
    +        test, the parsed results or where the parse failed. Quick and easy way to
    +        run a parse expression against a list of sample strings.
    +           
    +        Parameters:
    +         - tests - a list of separate test strings, or a multiline string of test strings
    +         - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests           
    +         - comment - (default=C{'#'}) - expression for indicating embedded comments in the test 
    +              string; pass None to disable comment filtering
    +         - fullDump - (default=C{True}) - dump results as list followed by results names in nested outline;
    +              if False, only dump nested list
    +         - printResults - (default=C{True}) prints test output to stdout
    +         - failureTests - (default=C{False}) indicates if these tests are expected to fail parsing
    +
    +        Returns: a (success, results) tuple, where success indicates that all tests succeeded
    +        (or failed if C{failureTests} is True), and the results contain a list of lines of each 
    +        test's output
    +        
    +        Example::
    +            number_expr = pyparsing_common.number.copy()
    +
    +            result = number_expr.runTests('''
    +                # unsigned integer
    +                100
    +                # negative integer
    +                -100
    +                # float with scientific notation
    +                6.02e23
    +                # integer with scientific notation
    +                1e-12
    +                ''')
    +            print("Success" if result[0] else "Failed!")
    +
    +            result = number_expr.runTests('''
    +                # stray character
    +                100Z
    +                # missing leading digit before '.'
    +                -.100
    +                # too many '.'
    +                3.14.159
    +                ''', failureTests=True)
    +            print("Success" if result[0] else "Failed!")
    +        prints::
    +            # unsigned integer
    +            100
    +            [100]
    +
    +            # negative integer
    +            -100
    +            [-100]
    +
    +            # float with scientific notation
    +            6.02e23
    +            [6.02e+23]
    +
    +            # integer with scientific notation
    +            1e-12
    +            [1e-12]
    +
    +            Success
    +            
    +            # stray character
    +            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)
    +
    +            # too many '.'
    +            3.14.159
    +                ^
    +            FAIL: Expected end of text (at char 4), (line:1, col:5)
    +
    +            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::
    +
    +            expr.runTest(r"this is a test\\n of strings that spans \\n 3 lines")
    +        
    +        (Note that this is a raw string literal, you must include the leading 'r'.)
    +        """
    +        if isinstance(tests, basestring):
    +            tests = list(map(str.strip, tests.rstrip().splitlines()))
    +        if isinstance(comment, basestring):
    +            comment = Literal(comment)
    +        allResults = []
    +        comments = []
    +        success = True
    +        for t in tests:
    +            if comment is not None and comment.matches(t, False) or comments and not t:
    +                comments.append(t)
    +                continue
    +            if not t:
    +                continue
    +            out = ['\n'.join(comments), t]
    +            comments = []
    +            try:
    +                t = t.replace(r'\n','\n')
    +                result = self.parseString(t, parseAll=parseAll)
    +                out.append(result.dump(full=fullDump))
    +                success = success and not failureTests
    +            except ParseBaseException as pe:
    +                fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else ""
    +                if '\n' in t:
    +                    out.append(line(pe.loc, t))
    +                    out.append(' '*(col(pe.loc,t)-1) + '^' + fatal)
    +                else:
    +                    out.append(' '*pe.loc + '^' + fatal)
    +                out.append("FAIL: " + str(pe))
    +                success = success and failureTests
    +                result = pe
    +            except Exception as exc:
    +                out.append("FAIL-EXCEPTION: " + str(exc))
    +                success = success and failureTests
    +                result = exc
    +
    +            if printResults:
    +                if fullDump:
    +                    out.append('')
    +                print('\n'.join(out))
    +
    +            allResults.append((t, result))
    +        
    +        return success, allResults
    +
    +        
    +class Token(ParserElement):
    +    """
    +    Abstract C{ParserElement} subclass, for defining atomic matching patterns.
    +    """
    +    def __init__( self ):
    +        super(Token,self).__init__( savelist=False )
    +
    +
    +class Empty(Token):
    +    """
    +    An empty token, will always match.
    +    """
    +    def __init__( self ):
    +        super(Empty,self).__init__()
    +        self.name = "Empty"
    +        self.mayReturnEmpty = True
    +        self.mayIndexError = False
    +
    +
    +class NoMatch(Token):
    +    """
    +    A token that will never match.
    +    """
    +    def __init__( self ):
    +        super(NoMatch,self).__init__()
    +        self.name = "NoMatch"
    +        self.mayReturnEmpty = True
    +        self.mayIndexError = False
    +        self.errmsg = "Unmatchable token"
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        raise ParseException(instring, loc, self.errmsg, self)
    +
    +
    +class Literal(Token):
    +    """
    +    Token to exactly match a specified string.
    +    
    +    Example::
    +        Literal('blah').parseString('blah')  # -> ['blah']
    +        Literal('blah').parseString('blahfooblah')  # -> ['blah']
    +        Literal('blah').parseString('bla')  # -> Exception: Expected "blah"
    +    
    +    For case-insensitive matching, use L{CaselessLiteral}.
    +    
    +    For keyword matching (force word break before and after the matched string),
    +    use L{Keyword} or L{CaselessKeyword}.
    +    """
    +    def __init__( self, matchString ):
    +        super(Literal,self).__init__()
    +        self.match = matchString
    +        self.matchLen = len(matchString)
    +        try:
    +            self.firstMatchChar = matchString[0]
    +        except IndexError:
    +            warnings.warn("null string passed to Literal; use Empty() instead",
    +                            SyntaxWarning, stacklevel=2)
    +            self.__class__ = Empty
    +        self.name = '"%s"' % _ustr(self.match)
    +        self.errmsg = "Expected " + self.name
    +        self.mayReturnEmpty = False
    +        self.mayIndexError = False
    +
    +    # Performance tuning: this routine gets called a *lot*
    +    # if this is a single character match string  and the first character matches,
    +    # short-circuit as quickly as possible, and avoid calling startswith
    +    #~ @profile
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        if (instring[loc] == self.firstMatchChar and
    +            (self.matchLen==1 or instring.startswith(self.match,loc)) ):
    +            return loc+self.matchLen, self.match
    +        raise ParseException(instring, loc, self.errmsg, self)
    +_L = Literal
    +ParserElement._literalStringClass = Literal
    +
    +class Keyword(Token):
    +    """
    +    Token to exactly match a specified string as a keyword, that is, it must be
    +    immediately followed by a non-keyword character.  Compare with C{L{Literal}}:
    +     - C{Literal("if")} will match the leading C{'if'} in C{'ifAndOnlyIf'}.
    +     - C{Keyword("if")} will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'}
    +    Accepts two optional constructor arguments in addition to the keyword string:
    +     - C{identChars} is a string of characters that would be valid identifier characters,
    +          defaulting to all alphanumerics + "_" and "$"
    +     - C{caseless} allows case-insensitive matching, default is C{False}.
    +       
    +    Example::
    +        Keyword("start").parseString("start")  # -> ['start']
    +        Keyword("start").parseString("starting")  # -> Exception
    +
    +    For case-insensitive matching, use L{CaselessKeyword}.
    +    """
    +    DEFAULT_KEYWORD_CHARS = alphanums+"_$"
    +
    +    def __init__( self, matchString, identChars=None, caseless=False ):
    +        super(Keyword,self).__init__()
    +        if identChars is None:
    +            identChars = Keyword.DEFAULT_KEYWORD_CHARS
    +        self.match = matchString
    +        self.matchLen = len(matchString)
    +        try:
    +            self.firstMatchChar = matchString[0]
    +        except IndexError:
    +            warnings.warn("null string passed to Keyword; use Empty() instead",
    +                            SyntaxWarning, stacklevel=2)
    +        self.name = '"%s"' % self.match
    +        self.errmsg = "Expected " + self.name
    +        self.mayReturnEmpty = False
    +        self.mayIndexError = False
    +        self.caseless = caseless
    +        if caseless:
    +            self.caselessmatch = matchString.upper()
    +            identChars = identChars.upper()
    +        self.identChars = set(identChars)
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        if self.caseless:
    +            if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and
    +                 (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and
    +                 (loc == 0 or instring[loc-1].upper() not in self.identChars) ):
    +                return loc+self.matchLen, self.match
    +        else:
    +            if (instring[loc] == self.firstMatchChar and
    +                (self.matchLen==1 or instring.startswith(self.match,loc)) and
    +                (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and
    +                (loc == 0 or instring[loc-1] not in self.identChars) ):
    +                return loc+self.matchLen, self.match
    +        raise ParseException(instring, loc, self.errmsg, self)
    +
    +    def copy(self):
    +        c = super(Keyword,self).copy()
    +        c.identChars = Keyword.DEFAULT_KEYWORD_CHARS
    +        return c
    +
    +    @staticmethod
    +    def setDefaultKeywordChars( chars ):
    +        """Overrides the default Keyword chars
    +        """
    +        Keyword.DEFAULT_KEYWORD_CHARS = chars
    +
    +class CaselessLiteral(Literal):
    +    """
    +    Token to match a specified string, ignoring case of letters.
    +    Note: the matched results will always be in the case of the given
    +    match string, NOT the case of the input text.
    +
    +    Example::
    +        OneOrMore(CaselessLiteral("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD', 'CMD']
    +        
    +    (Contrast with example for L{CaselessKeyword}.)
    +    """
    +    def __init__( self, matchString ):
    +        super(CaselessLiteral,self).__init__( matchString.upper() )
    +        # Preserve the defining literal.
    +        self.returnString = matchString
    +        self.name = "'%s'" % self.returnString
    +        self.errmsg = "Expected " + self.name
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        if instring[ loc:loc+self.matchLen ].upper() == self.match:
    +            return loc+self.matchLen, self.returnString
    +        raise ParseException(instring, loc, self.errmsg, self)
    +
    +class CaselessKeyword(Keyword):
    +    """
    +    Caseless version of L{Keyword}.
    +
    +    Example::
    +        OneOrMore(CaselessKeyword("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD']
    +        
    +    (Contrast with example for L{CaselessLiteral}.)
    +    """
    +    def __init__( self, matchString, identChars=None ):
    +        super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True )
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and
    +             (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ):
    +            return loc+self.matchLen, self.match
    +        raise ParseException(instring, loc, self.errmsg, self)
    +
    +class CloseMatch(Token):
    +    """
    +    A variation on L{Literal} which matches "close" matches, that is, 
    +    strings with at most 'n' mismatching characters. C{CloseMatch} takes parameters:
    +     - C{match_string} - string to be matched
    +     - C{maxMismatches} - (C{default=1}) maximum number of mismatches allowed to count as a match
    +    
    +    The results from a successful parse will contain the matched text from the input string and the following named results:
    +     - C{mismatches} - a list of the positions within the match_string where mismatches were found
    +     - C{original} - the original match_string used to compare against the input string
    +    
    +    If C{mismatches} is an empty list, then the match was an exact match.
    +    
    +    Example::
    +        patt = CloseMatch("ATCATCGAATGGA")
    +        patt.parseString("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']})
    +        patt.parseString("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1)
    +
    +        # exact match
    +        patt.parseString("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']})
    +
    +        # close match allowing up to 2 mismatches
    +        patt = CloseMatch("ATCATCGAATGGA", maxMismatches=2)
    +        patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']})
    +    """
    +    def __init__(self, match_string, maxMismatches=1):
    +        super(CloseMatch,self).__init__()
    +        self.name = match_string
    +        self.match_string = match_string
    +        self.maxMismatches = maxMismatches
    +        self.errmsg = "Expected %r (with up to %d mismatches)" % (self.match_string, self.maxMismatches)
    +        self.mayIndexError = False
    +        self.mayReturnEmpty = False
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        start = loc
    +        instrlen = len(instring)
    +        maxloc = start + len(self.match_string)
    +
    +        if maxloc <= instrlen:
    +            match_string = self.match_string
    +            match_stringloc = 0
    +            mismatches = []
    +            maxMismatches = self.maxMismatches
    +
    +            for match_stringloc,s_m in enumerate(zip(instring[loc:maxloc], self.match_string)):
    +                src,mat = s_m
    +                if src != mat:
    +                    mismatches.append(match_stringloc)
    +                    if len(mismatches) > maxMismatches:
    +                        break
    +            else:
    +                loc = match_stringloc + 1
    +                results = ParseResults([instring[start:loc]])
    +                results['original'] = self.match_string
    +                results['mismatches'] = mismatches
    +                return loc, results
    +
    +        raise ParseException(instring, loc, self.errmsg, self)
    +
    +
    +class Word(Token):
    +    """
    +    Token for matching words composed of allowed character sets.
    +    Defined with string containing all allowed initial characters,
    +    an optional string containing allowed body characters (if omitted,
    +    defaults to the initial character set), and an optional minimum,
    +    maximum, and/or exact length.  The default value for C{min} is 1 (a
    +    minimum value < 1 is not valid); the default values for C{max} and C{exact}
    +    are 0, meaning no maximum or exact length restriction. An optional
    +    C{excludeChars} parameter can list characters that might be found in 
    +    the input C{bodyChars} string; useful to define a word of all printables
    +    except for one or two characters, for instance.
    +    
    +    L{srange} is useful for defining custom character set strings for defining 
    +    C{Word} expressions, using range notation from regular expression character sets.
    +    
    +    A common mistake is to use C{Word} to match a specific literal string, as in 
    +    C{Word("Address")}. Remember that C{Word} uses the string argument to define
    +    I{sets} of matchable characters. This expression would match "Add", "AAA",
    +    "dAred", or any other word made up of the characters 'A', 'd', 'r', 'e', and 's'.
    +    To match an exact literal string, use L{Literal} or L{Keyword}.
    +
    +    pyparsing includes helper strings for building Words:
    +     - L{alphas}
    +     - L{nums}
    +     - L{alphanums}
    +     - L{hexnums}
    +     - L{alphas8bit} (alphabetic characters in ASCII range 128-255 - accented, tilded, umlauted, etc.)
    +     - L{punc8bit} (non-alphabetic characters in ASCII range 128-255 - currency, symbols, superscripts, diacriticals, etc.)
    +     - L{printables} (any non-whitespace character)
    +
    +    Example::
    +        # a word composed of digits
    +        integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9"))
    +        
    +        # a word with a leading capital, and zero or more lowercase
    +        capital_word = Word(alphas.upper(), alphas.lower())
    +
    +        # hostnames are alphanumeric, with leading alpha, and '-'
    +        hostname = Word(alphas, alphanums+'-')
    +        
    +        # 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, excludeChars=",")
    +    """
    +    def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ):
    +        super(Word,self).__init__()
    +        if excludeChars:
    +            initChars = ''.join(c for c in initChars if c not in excludeChars)
    +            if bodyChars:
    +                bodyChars = ''.join(c for c in bodyChars if c not in excludeChars)
    +        self.initCharsOrig = initChars
    +        self.initChars = set(initChars)
    +        if bodyChars :
    +            self.bodyCharsOrig = bodyChars
    +            self.bodyChars = set(bodyChars)
    +        else:
    +            self.bodyCharsOrig = initChars
    +            self.bodyChars = set(initChars)
    +
    +        self.maxSpecified = max > 0
    +
    +        if min < 1:
    +            raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted")
    +
    +        self.minLen = min
    +
    +        if max > 0:
    +            self.maxLen = max
    +        else:
    +            self.maxLen = _MAX_INT
    +
    +        if exact > 0:
    +            self.maxLen = exact
    +            self.minLen = exact
    +
    +        self.name = _ustr(self)
    +        self.errmsg = "Expected " + self.name
    +        self.mayIndexError = False
    +        self.asKeyword = asKeyword
    +
    +        if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0):
    +            if self.bodyCharsOrig == self.initCharsOrig:
    +                self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig)
    +            elif len(self.initCharsOrig) == 1:
    +                self.reString = "%s[%s]*" % \
    +                                      (re.escape(self.initCharsOrig),
    +                                      _escapeRegexRangeChars(self.bodyCharsOrig),)
    +            else:
    +                self.reString = "[%s][%s]*" % \
    +                                      (_escapeRegexRangeChars(self.initCharsOrig),
    +                                      _escapeRegexRangeChars(self.bodyCharsOrig),)
    +            if self.asKeyword:
    +                self.reString = r"\b"+self.reString+r"\b"
    +            try:
    +                self.re = re.compile( self.reString )
    +            except Exception:
    +                self.re = None
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        if self.re:
    +            result = self.re.match(instring,loc)
    +            if not result:
    +                raise ParseException(instring, loc, self.errmsg, self)
    +
    +            loc = result.end()
    +            return loc, result.group()
    +
    +        if not(instring[ loc ] in self.initChars):
    +            raise ParseException(instring, loc, self.errmsg, self)
    +
    +        start = loc
    +        loc += 1
    +        instrlen = len(instring)
    +        bodychars = self.bodyChars
    +        maxloc = start + self.maxLen
    +        maxloc = min( maxloc, instrlen )
    +        while loc < maxloc and instring[loc] in bodychars:
    +            loc += 1
    +
    +        throwException = False
    +        if loc - start < self.minLen:
    +            throwException = True
    +        if self.maxSpecified and loc < instrlen and instring[loc] in bodychars:
    +            throwException = True
    +        if self.asKeyword:
    +            if (start>0 and instring[start-1] in bodychars) or (loc4:
    +                    return s[:4]+"..."
    +                else:
    +                    return s
    +
    +            if ( self.initCharsOrig != self.bodyCharsOrig ):
    +                self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) )
    +            else:
    +                self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig)
    +
    +        return self.strRepr
    +
    +
    +class Regex(Token):
    +    r"""
    +    Token for matching strings that match a given regular expression.
    +    Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module.
    +    If the given regex contains named groups (defined using C{(?P...)}), these will be preserved as 
    +    named parse results.
    +
    +    Example::
    +        realnum = Regex(r"[+-]?\d+\.\d*")
    +        date = Regex(r'(?P\d{4})-(?P\d\d?)-(?P\d\d?)')
    +        # ref: http://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression
    +        roman = Regex(r"M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})")
    +    """
    +    compiledREtype = type(re.compile("[A-Z]"))
    +    def __init__( self, pattern, flags=0):
    +        """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags."""
    +        super(Regex,self).__init__()
    +
    +        if isinstance(pattern, basestring):
    +            if not pattern:
    +                warnings.warn("null string passed to Regex; use Empty() instead",
    +                        SyntaxWarning, stacklevel=2)
    +
    +            self.pattern = pattern
    +            self.flags = flags
    +
    +            try:
    +                self.re = re.compile(self.pattern, self.flags)
    +                self.reString = self.pattern
    +            except sre_constants.error:
    +                warnings.warn("invalid pattern (%s) passed to Regex" % pattern,
    +                    SyntaxWarning, stacklevel=2)
    +                raise
    +
    +        elif isinstance(pattern, Regex.compiledREtype):
    +            self.re = pattern
    +            self.pattern = \
    +            self.reString = str(pattern)
    +            self.flags = flags
    +            
    +        else:
    +            raise ValueError("Regex may only be constructed with a string or a compiled RE object")
    +
    +        self.name = _ustr(self)
    +        self.errmsg = "Expected " + self.name
    +        self.mayIndexError = False
    +        self.mayReturnEmpty = True
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        result = self.re.match(instring,loc)
    +        if not result:
    +            raise ParseException(instring, loc, self.errmsg, self)
    +
    +        loc = result.end()
    +        d = result.groupdict()
    +        ret = ParseResults(result.group())
    +        if d:
    +            for k in d:
    +                ret[k] = d[k]
    +        return loc,ret
    +
    +    def __str__( self ):
    +        try:
    +            return super(Regex,self).__str__()
    +        except Exception:
    +            pass
    +
    +        if self.strRepr is None:
    +            self.strRepr = "Re:(%s)" % repr(self.pattern)
    +
    +        return self.strRepr
    +
    +
    +class QuotedString(Token):
    +    r"""
    +    Token for matching strings that are delimited by quoting characters.
    +    
    +    Defined with the following parameters:
    +        - quoteChar - string of one or more characters defining the quote delimiting string
    +        - escChar - character to escape quotes, typically backslash (default=C{None})
    +        - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=C{None})
    +        - multiline - boolean indicating whether quotes can span multiple lines (default=C{False})
    +        - unquoteResults - boolean indicating whether the matched text should be unquoted (default=C{True})
    +        - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=C{None} => same as quoteChar)
    +        - convertWhitespaceEscapes - convert escaped whitespace (C{'\t'}, C{'\n'}, etc.) to actual whitespace (default=C{True})
    +
    +    Example::
    +        qs = QuotedString('"')
    +        print(qs.searchString('lsjdf "This is the quote" sldjf'))
    +        complex_qs = QuotedString('{{', endQuoteChar='}}')
    +        print(complex_qs.searchString('lsjdf {{This is the "quote"}} sldjf'))
    +        sql_qs = QuotedString('"', escQuote='""')
    +        print(sql_qs.searchString('lsjdf "This is the quote with ""embedded"" quotes" sldjf'))
    +    prints::
    +        [['This is the quote']]
    +        [['This is the "quote"']]
    +        [['This is the quote with "embedded" quotes']]
    +    """
    +    def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True):
    +        super(QuotedString,self).__init__()
    +
    +        # remove white space from quote chars - wont work anyway
    +        quoteChar = quoteChar.strip()
    +        if not quoteChar:
    +            warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
    +            raise SyntaxError()
    +
    +        if endQuoteChar is None:
    +            endQuoteChar = quoteChar
    +        else:
    +            endQuoteChar = endQuoteChar.strip()
    +            if not endQuoteChar:
    +                warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
    +                raise SyntaxError()
    +
    +        self.quoteChar = quoteChar
    +        self.quoteCharLen = len(quoteChar)
    +        self.firstQuoteChar = quoteChar[0]
    +        self.endQuoteChar = endQuoteChar
    +        self.endQuoteCharLen = len(endQuoteChar)
    +        self.escChar = escChar
    +        self.escQuote = escQuote
    +        self.unquoteResults = unquoteResults
    +        self.convertWhitespaceEscapes = convertWhitespaceEscapes
    +
    +        if multiline:
    +            self.flags = re.MULTILINE | re.DOTALL
    +            self.pattern = r'%s(?:[^%s%s]' % \
    +                ( re.escape(self.quoteChar),
    +                  _escapeRegexRangeChars(self.endQuoteChar[0]),
    +                  (escChar is not None and _escapeRegexRangeChars(escChar) or '') )
    +        else:
    +            self.flags = 0
    +            self.pattern = r'%s(?:[^%s\n\r%s]' % \
    +                ( re.escape(self.quoteChar),
    +                  _escapeRegexRangeChars(self.endQuoteChar[0]),
    +                  (escChar is not None and _escapeRegexRangeChars(escChar) or '') )
    +        if len(self.endQuoteChar) > 1:
    +            self.pattern += (
    +                '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]),
    +                                               _escapeRegexRangeChars(self.endQuoteChar[i]))
    +                                    for i in range(len(self.endQuoteChar)-1,0,-1)) + ')'
    +                )
    +        if escQuote:
    +            self.pattern += (r'|(?:%s)' % re.escape(escQuote))
    +        if escChar:
    +            self.pattern += (r'|(?:%s.)' % re.escape(escChar))
    +            self.escCharReplacePattern = re.escape(self.escChar)+"(.)"
    +        self.pattern += (r')*%s' % re.escape(self.endQuoteChar))
    +
    +        try:
    +            self.re = re.compile(self.pattern, self.flags)
    +            self.reString = self.pattern
    +        except sre_constants.error:
    +            warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern,
    +                SyntaxWarning, stacklevel=2)
    +            raise
    +
    +        self.name = _ustr(self)
    +        self.errmsg = "Expected " + self.name
    +        self.mayIndexError = False
    +        self.mayReturnEmpty = True
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None
    +        if not result:
    +            raise ParseException(instring, loc, self.errmsg, self)
    +
    +        loc = result.end()
    +        ret = result.group()
    +
    +        if self.unquoteResults:
    +
    +            # strip off quotes
    +            ret = ret[self.quoteCharLen:-self.endQuoteCharLen]
    +
    +            if isinstance(ret,basestring):
    +                # replace escaped whitespace
    +                if '\\' in ret and self.convertWhitespaceEscapes:
    +                    ws_map = {
    +                        r'\t' : '\t',
    +                        r'\n' : '\n',
    +                        r'\f' : '\f',
    +                        r'\r' : '\r',
    +                    }
    +                    for wslit,wschar in ws_map.items():
    +                        ret = ret.replace(wslit, wschar)
    +
    +                # replace escaped characters
    +                if self.escChar:
    +                    ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret)
    +
    +                # replace escaped quotes
    +                if self.escQuote:
    +                    ret = ret.replace(self.escQuote, self.endQuoteChar)
    +
    +        return loc, ret
    +
    +    def __str__( self ):
    +        try:
    +            return super(QuotedString,self).__str__()
    +        except Exception:
    +            pass
    +
    +        if self.strRepr is None:
    +            self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar)
    +
    +        return self.strRepr
    +
    +
    +class CharsNotIn(Token):
    +    """
    +    Token for matching words composed of characters I{not} in a given set (will
    +    include whitespace in matched characters if not listed in the provided exclusion set - see example).
    +    Defined with string containing all disallowed characters, and an optional
    +    minimum, maximum, and/or exact length.  The default value for C{min} is 1 (a
    +    minimum value < 1 is not valid); the default values for C{max} and C{exact}
    +    are 0, meaning no maximum or exact length restriction.
    +
    +    Example::
    +        # define a comma-separated-value as anything that is not a ','
    +        csv_value = CharsNotIn(',')
    +        print(delimitedList(csv_value).parseString("dkls,lsdkjf,s12 34,@!#,213"))
    +    prints::
    +        ['dkls', 'lsdkjf', 's12 34', '@!#', '213']
    +    """
    +    def __init__( self, notChars, min=1, max=0, exact=0 ):
    +        super(CharsNotIn,self).__init__()
    +        self.skipWhitespace = False
    +        self.notChars = notChars
    +
    +        if min < 1:
    +            raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted")
    +
    +        self.minLen = min
    +
    +        if max > 0:
    +            self.maxLen = max
    +        else:
    +            self.maxLen = _MAX_INT
    +
    +        if exact > 0:
    +            self.maxLen = exact
    +            self.minLen = exact
    +
    +        self.name = _ustr(self)
    +        self.errmsg = "Expected " + self.name
    +        self.mayReturnEmpty = ( self.minLen == 0 )
    +        self.mayIndexError = False
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        if instring[loc] in self.notChars:
    +            raise ParseException(instring, loc, self.errmsg, self)
    +
    +        start = loc
    +        loc += 1
    +        notchars = self.notChars
    +        maxlen = min( start+self.maxLen, len(instring) )
    +        while loc < maxlen and \
    +              (instring[loc] not in notchars):
    +            loc += 1
    +
    +        if loc - start < self.minLen:
    +            raise ParseException(instring, loc, self.errmsg, self)
    +
    +        return loc, instring[start:loc]
    +
    +    def __str__( self ):
    +        try:
    +            return super(CharsNotIn, self).__str__()
    +        except Exception:
    +            pass
    +
    +        if self.strRepr is None:
    +            if len(self.notChars) > 4:
    +                self.strRepr = "!W:(%s...)" % self.notChars[:4]
    +            else:
    +                self.strRepr = "!W:(%s)" % self.notChars
    +
    +        return self.strRepr
    +
    +class White(Token):
    +    """
    +    Special matching class for matching whitespace.  Normally, whitespace is ignored
    +    by pyparsing grammars.  This class is included when some whitespace structures
    +    are significant.  Define with a string containing the whitespace characters to be
    +    matched; default is C{" \\t\\r\\n"}.  Also takes optional C{min}, C{max}, and C{exact} arguments,
    +    as defined for the C{L{Word}} class.
    +    """
    +    whiteStrs = {
    +        " " : "",
    +        "\t": "",
    +        "\n": "",
    +        "\r": "",
    +        "\f": "",
    +        }
    +    def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0):
    +        super(White,self).__init__()
    +        self.matchWhite = ws
    +        self.setWhitespaceChars( "".join(c for c in self.whiteChars if c not in self.matchWhite) )
    +        #~ self.leaveWhitespace()
    +        self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite))
    +        self.mayReturnEmpty = True
    +        self.errmsg = "Expected " + self.name
    +
    +        self.minLen = min
    +
    +        if max > 0:
    +            self.maxLen = max
    +        else:
    +            self.maxLen = _MAX_INT
    +
    +        if exact > 0:
    +            self.maxLen = exact
    +            self.minLen = exact
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        if not(instring[ loc ] in self.matchWhite):
    +            raise ParseException(instring, loc, self.errmsg, self)
    +        start = loc
    +        loc += 1
    +        maxloc = start + self.maxLen
    +        maxloc = min( maxloc, len(instring) )
    +        while loc < maxloc and instring[loc] in self.matchWhite:
    +            loc += 1
    +
    +        if loc - start < self.minLen:
    +            raise ParseException(instring, loc, self.errmsg, self)
    +
    +        return loc, instring[start:loc]
    +
    +
    +class _PositionToken(Token):
    +    def __init__( self ):
    +        super(_PositionToken,self).__init__()
    +        self.name=self.__class__.__name__
    +        self.mayReturnEmpty = True
    +        self.mayIndexError = False
    +
    +class GoToColumn(_PositionToken):
    +    """
    +    Token to advance to a specific column of input text; useful for tabular report scraping.
    +    """
    +    def __init__( self, colno ):
    +        super(GoToColumn,self).__init__()
    +        self.col = colno
    +
    +    def preParse( self, instring, loc ):
    +        if col(loc,instring) != self.col:
    +            instrlen = len(instring)
    +            if self.ignoreExprs:
    +                loc = self._skipIgnorables( instring, loc )
    +            while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col :
    +                loc += 1
    +        return loc
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        thiscol = col( loc, instring )
    +        if thiscol > self.col:
    +            raise ParseException( instring, loc, "Text not in expected column", self )
    +        newloc = loc + self.col - thiscol
    +        ret = instring[ loc: newloc ]
    +        return newloc, ret
    +
    +
    +class LineStart(_PositionToken):
    +    """
    +    Matches if current position is at the beginning of a line within the parse string
    +    
    +    Example::
    +    
    +        test = '''\
    +        AAA this line
    +        AAA and this line
    +          AAA but not this one
    +        B AAA and definitely not this one
    +        '''
    +
    +        for t in (LineStart() + 'AAA' + restOfLine).searchString(test):
    +            print(t)
    +    
    +    Prints::
    +        ['AAA', ' this line']
    +        ['AAA', ' and this line']    
    +
    +    """
    +    def __init__( self ):
    +        super(LineStart,self).__init__()
    +        self.errmsg = "Expected start of line"
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        if col(loc, instring) == 1:
    +            return loc, []
    +        raise ParseException(instring, loc, self.errmsg, self)
    +
    +class LineEnd(_PositionToken):
    +    """
    +    Matches if current position is at the end of a line within the parse string
    +    """
    +    def __init__( self ):
    +        super(LineEnd,self).__init__()
    +        self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") )
    +        self.errmsg = "Expected end of line"
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        if loc len(instring):
    +            return loc, []
    +        else:
    +            raise ParseException(instring, loc, self.errmsg, self)
    +
    +class WordStart(_PositionToken):
    +    """
    +    Matches if the current position is at the beginning of a Word, and
    +    is not preceded by any character in a given set of C{wordChars}
    +    (default=C{printables}). To emulate the C{\b} behavior of regular expressions,
    +    use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of
    +    the string being parsed, or at the beginning of a line.
    +    """
    +    def __init__(self, wordChars = printables):
    +        super(WordStart,self).__init__()
    +        self.wordChars = set(wordChars)
    +        self.errmsg = "Not at the start of a word"
    +
    +    def parseImpl(self, instring, loc, doActions=True ):
    +        if loc != 0:
    +            if (instring[loc-1] in self.wordChars or
    +                instring[loc] not in self.wordChars):
    +                raise ParseException(instring, loc, self.errmsg, self)
    +        return loc, []
    +
    +class WordEnd(_PositionToken):
    +    """
    +    Matches if the current position is at the end of a Word, and
    +    is not followed by any character in a given set of C{wordChars}
    +    (default=C{printables}). To emulate the C{\b} behavior of regular expressions,
    +    use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of
    +    the string being parsed, or at the end of a line.
    +    """
    +    def __init__(self, wordChars = printables):
    +        super(WordEnd,self).__init__()
    +        self.wordChars = set(wordChars)
    +        self.skipWhitespace = False
    +        self.errmsg = "Not at the end of a word"
    +
    +    def parseImpl(self, instring, loc, doActions=True ):
    +        instrlen = len(instring)
    +        if instrlen>0 and loc maxExcLoc:
    +                    maxException = err
    +                    maxExcLoc = err.loc
    +            except IndexError:
    +                if len(instring) > maxExcLoc:
    +                    maxException = ParseException(instring,len(instring),e.errmsg,self)
    +                    maxExcLoc = len(instring)
    +            else:
    +                # save match among all matches, to retry longest to shortest
    +                matches.append((loc2, e))
    +
    +        if matches:
    +            matches.sort(key=lambda x: -x[0])
    +            for _,e in matches:
    +                try:
    +                    return e._parse( instring, loc, doActions )
    +                except ParseException as err:
    +                    err.__traceback__ = None
    +                    if err.loc > maxExcLoc:
    +                        maxException = err
    +                        maxExcLoc = err.loc
    +
    +        if maxException is not None:
    +            maxException.msg = self.errmsg
    +            raise maxException
    +        else:
    +            raise ParseException(instring, loc, "no defined alternatives to match", self)
    +
    +
    +    def __ixor__(self, other ):
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass( other )
    +        return self.append( other ) #Or( [ self, other ] )
    +
    +    def __str__( self ):
    +        if hasattr(self,"name"):
    +            return self.name
    +
    +        if self.strRepr is None:
    +            self.strRepr = "{" + " ^ ".join(_ustr(e) for e in self.exprs) + "}"
    +
    +        return self.strRepr
    +
    +    def checkRecursion( self, parseElementList ):
    +        subRecCheckList = parseElementList[:] + [ self ]
    +        for e in self.exprs:
    +            e.checkRecursion( subRecCheckList )
    +
    +
    +class MatchFirst(ParseExpression):
    +    """
    +    Requires that at least one C{ParseExpression} is found.
    +    If two expressions match, the first one listed is the one that will match.
    +    May be constructed using the C{'|'} operator.
    +
    +    Example::
    +        # construct MatchFirst using '|' operator
    +        
    +        # watch the order of expressions to match
    +        number = Word(nums) | Combine(Word(nums) + '.' + Word(nums))
    +        print(number.searchString("123 3.1416 789")) #  Fail! -> [['123'], ['3'], ['1416'], ['789']]
    +
    +        # put more selective expression first
    +        number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums)
    +        print(number.searchString("123 3.1416 789")) #  Better -> [['123'], ['3.1416'], ['789']]
    +    """
    +    def __init__( self, exprs, savelist = False ):
    +        super(MatchFirst,self).__init__(exprs, savelist)
    +        if self.exprs:
    +            self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
    +        else:
    +            self.mayReturnEmpty = True
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        maxExcLoc = -1
    +        maxException = None
    +        for e in self.exprs:
    +            try:
    +                ret = e._parse( instring, loc, doActions )
    +                return ret
    +            except ParseException as err:
    +                if err.loc > maxExcLoc:
    +                    maxException = err
    +                    maxExcLoc = err.loc
    +            except IndexError:
    +                if len(instring) > maxExcLoc:
    +                    maxException = ParseException(instring,len(instring),e.errmsg,self)
    +                    maxExcLoc = len(instring)
    +
    +        # only got here if no expression matched, raise exception for match that made it the furthest
    +        else:
    +            if maxException is not None:
    +                maxException.msg = self.errmsg
    +                raise maxException
    +            else:
    +                raise ParseException(instring, loc, "no defined alternatives to match", self)
    +
    +    def __ior__(self, other ):
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass( other )
    +        return self.append( other ) #MatchFirst( [ self, other ] )
    +
    +    def __str__( self ):
    +        if hasattr(self,"name"):
    +            return self.name
    +
    +        if self.strRepr is None:
    +            self.strRepr = "{" + " | ".join(_ustr(e) for e in self.exprs) + "}"
    +
    +        return self.strRepr
    +
    +    def checkRecursion( self, parseElementList ):
    +        subRecCheckList = parseElementList[:] + [ self ]
    +        for e in self.exprs:
    +            e.checkRecursion( subRecCheckList )
    +
    +
    +class Each(ParseExpression):
    +    """
    +    Requires all given C{ParseExpression}s to be found, but in any order.
    +    Expressions may be separated by whitespace.
    +    May be constructed using the C{'&'} operator.
    +
    +    Example::
    +        color = oneOf("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN")
    +        shape_type = oneOf("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON")
    +        integer = Word(nums)
    +        shape_attr = "shape:" + shape_type("shape")
    +        posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn")
    +        color_attr = "color:" + color("color")
    +        size_attr = "size:" + integer("size")
    +
    +        # use Each (using operator '&') to accept attributes in any order 
    +        # (shape and posn are required, color and size are optional)
    +        shape_spec = shape_attr & posn_attr & Optional(color_attr) & Optional(size_attr)
    +
    +        shape_spec.runTests('''
    +            shape: SQUARE color: BLACK posn: 100, 120
    +            shape: CIRCLE size: 50 color: BLUE posn: 50,80
    +            color:GREEN size:20 shape:TRIANGLE posn:20,40
    +            '''
    +            )
    +    prints::
    +        shape: SQUARE color: BLACK posn: 100, 120
    +        ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']]
    +        - color: BLACK
    +        - posn: ['100', ',', '120']
    +          - 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
    +        - posn: ['50', ',', '80']
    +          - 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
    +        - posn: ['20', ',', '40']
    +          - x: 20
    +          - y: 40
    +        - shape: TRIANGLE
    +        - size: 20
    +    """
    +    def __init__( self, exprs, savelist = True ):
    +        super(Each,self).__init__(exprs, savelist)
    +        self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
    +        self.skipWhitespace = True
    +        self.initExprGroups = True
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        if self.initExprGroups:
    +            self.opt1map = dict((id(e.expr),e) for e in self.exprs if isinstance(e,Optional))
    +            opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ]
    +            opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)]
    +            self.optionals = opt1 + opt2
    +            self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ]
    +            self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ]
    +            self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ]
    +            self.required += self.multirequired
    +            self.initExprGroups = False
    +        tmpLoc = loc
    +        tmpReqd = self.required[:]
    +        tmpOpt  = self.optionals[:]
    +        matchOrder = []
    +
    +        keepMatching = True
    +        while keepMatching:
    +            tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired
    +            failed = []
    +            for e in tmpExprs:
    +                try:
    +                    tmpLoc = e.tryParse( instring, tmpLoc )
    +                except ParseException:
    +                    failed.append(e)
    +                else:
    +                    matchOrder.append(self.opt1map.get(id(e),e))
    +                    if e in tmpReqd:
    +                        tmpReqd.remove(e)
    +                    elif e in tmpOpt:
    +                        tmpOpt.remove(e)
    +            if len(failed) == len(tmpExprs):
    +                keepMatching = False
    +
    +        if tmpReqd:
    +            missing = ", ".join(_ustr(e) for e in tmpReqd)
    +            raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing )
    +
    +        # add any unmatched Optionals, in case they have default values defined
    +        matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt]
    +
    +        resultlist = []
    +        for e in matchOrder:
    +            loc,results = e._parse(instring,loc,doActions)
    +            resultlist.append(results)
    +
    +        finalResults = sum(resultlist, ParseResults([]))
    +        return loc, finalResults
    +
    +    def __str__( self ):
    +        if hasattr(self,"name"):
    +            return self.name
    +
    +        if self.strRepr is None:
    +            self.strRepr = "{" + " & ".join(_ustr(e) for e in self.exprs) + "}"
    +
    +        return self.strRepr
    +
    +    def checkRecursion( self, parseElementList ):
    +        subRecCheckList = parseElementList[:] + [ self ]
    +        for e in self.exprs:
    +            e.checkRecursion( subRecCheckList )
    +
    +
    +class ParseElementEnhance(ParserElement):
    +    """
    +    Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens.
    +    """
    +    def __init__( self, expr, savelist=False ):
    +        super(ParseElementEnhance,self).__init__(savelist)
    +        if isinstance( expr, basestring ):
    +            if issubclass(ParserElement._literalStringClass, Token):
    +                expr = ParserElement._literalStringClass(expr)
    +            else:
    +                expr = ParserElement._literalStringClass(Literal(expr))
    +        self.expr = expr
    +        self.strRepr = None
    +        if expr is not None:
    +            self.mayIndexError = expr.mayIndexError
    +            self.mayReturnEmpty = expr.mayReturnEmpty
    +            self.setWhitespaceChars( expr.whiteChars )
    +            self.skipWhitespace = expr.skipWhitespace
    +            self.saveAsList = expr.saveAsList
    +            self.callPreparse = expr.callPreparse
    +            self.ignoreExprs.extend(expr.ignoreExprs)
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        if self.expr is not None:
    +            return self.expr._parse( instring, loc, doActions, callPreParse=False )
    +        else:
    +            raise ParseException("",loc,self.errmsg,self)
    +
    +    def leaveWhitespace( self ):
    +        self.skipWhitespace = False
    +        self.expr = self.expr.copy()
    +        if self.expr is not None:
    +            self.expr.leaveWhitespace()
    +        return self
    +
    +    def ignore( self, other ):
    +        if isinstance( other, Suppress ):
    +            if other not in self.ignoreExprs:
    +                super( ParseElementEnhance, self).ignore( other )
    +                if self.expr is not None:
    +                    self.expr.ignore( self.ignoreExprs[-1] )
    +        else:
    +            super( ParseElementEnhance, self).ignore( other )
    +            if self.expr is not None:
    +                self.expr.ignore( self.ignoreExprs[-1] )
    +        return self
    +
    +    def streamline( self ):
    +        super(ParseElementEnhance,self).streamline()
    +        if self.expr is not None:
    +            self.expr.streamline()
    +        return self
    +
    +    def checkRecursion( self, parseElementList ):
    +        if self in parseElementList:
    +            raise RecursiveGrammarException( parseElementList+[self] )
    +        subRecCheckList = parseElementList[:] + [ self ]
    +        if self.expr is not None:
    +            self.expr.checkRecursion( subRecCheckList )
    +
    +    def validate( self, validateTrace=[] ):
    +        tmp = validateTrace[:]+[self]
    +        if self.expr is not None:
    +            self.expr.validate(tmp)
    +        self.checkRecursion( [] )
    +
    +    def __str__( self ):
    +        try:
    +            return super(ParseElementEnhance,self).__str__()
    +        except Exception:
    +            pass
    +
    +        if self.strRepr is None and self.expr is not None:
    +            self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) )
    +        return self.strRepr
    +
    +
    +class FollowedBy(ParseElementEnhance):
    +    """
    +    Lookahead matching of the given parse expression.  C{FollowedBy}
    +    does I{not} advance the parsing position within the input string, it only
    +    verifies that the specified parse expression matches at the current
    +    position.  C{FollowedBy} always returns a null token list.
    +
    +    Example::
    +        # 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, stopOn=label).setParseAction(' '.join))
    +        
    +        OneOrMore(attr_expr).parseString("shape: SQUARE color: BLACK posn: upper left").pprint()
    +    prints::
    +        [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']]
    +    """
    +    def __init__( self, expr ):
    +        super(FollowedBy,self).__init__(expr)
    +        self.mayReturnEmpty = True
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        self.expr.tryParse( instring, loc )
    +        return loc, []
    +
    +
    +class NotAny(ParseElementEnhance):
    +    """
    +    Lookahead to disallow matching with the given parse expression.  C{NotAny}
    +    does I{not} advance the parsing position within the input string, it only
    +    verifies that the specified parse expression does I{not} match at the current
    +    position.  Also, C{NotAny} does I{not} skip over leading whitespace. C{NotAny}
    +    always returns a null token list.  May be constructed using the '~' operator.
    +
    +    Example::
    +        
    +    """
    +    def __init__( self, expr ):
    +        super(NotAny,self).__init__(expr)
    +        #~ self.leaveWhitespace()
    +        self.skipWhitespace = False  # do NOT use self.leaveWhitespace(), don't want to propagate to exprs
    +        self.mayReturnEmpty = True
    +        self.errmsg = "Found unwanted token, "+_ustr(self.expr)
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        if self.expr.canParseNext(instring, loc):
    +            raise ParseException(instring, loc, self.errmsg, self)
    +        return loc, []
    +
    +    def __str__( self ):
    +        if hasattr(self,"name"):
    +            return self.name
    +
    +        if self.strRepr is None:
    +            self.strRepr = "~{" + _ustr(self.expr) + "}"
    +
    +        return self.strRepr
    +
    +class _MultipleMatch(ParseElementEnhance):
    +    def __init__( self, expr, stopOn=None):
    +        super(_MultipleMatch, self).__init__(expr)
    +        self.saveAsList = True
    +        ender = stopOn
    +        if isinstance(ender, basestring):
    +            ender = ParserElement._literalStringClass(ender)
    +        self.not_ender = ~ender if ender is not None else None
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        self_expr_parse = self.expr._parse
    +        self_skip_ignorables = self._skipIgnorables
    +        check_ender = self.not_ender is not None
    +        if check_ender:
    +            try_not_ender = self.not_ender.tryParse
    +        
    +        # must be at least one (but first see if we are the stopOn sentinel;
    +        # if so, fail)
    +        if check_ender:
    +            try_not_ender(instring, loc)
    +        loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False )
    +        try:
    +            hasIgnoreExprs = (not not self.ignoreExprs)
    +            while 1:
    +                if check_ender:
    +                    try_not_ender(instring, loc)
    +                if hasIgnoreExprs:
    +                    preloc = self_skip_ignorables( instring, loc )
    +                else:
    +                    preloc = loc
    +                loc, tmptokens = self_expr_parse( instring, preloc, doActions )
    +                if tmptokens or tmptokens.haskeys():
    +                    tokens += tmptokens
    +        except (ParseException,IndexError):
    +            pass
    +
    +        return loc, tokens
    +        
    +class OneOrMore(_MultipleMatch):
    +    """
    +    Repetition of one or more of the given expression.
    +    
    +    Parameters:
    +     - expr - expression that must match one or more times
    +     - stopOn - (default=C{None}) - expression for a terminating sentinel
    +          (only required if the sentinel would ordinarily match the repetition 
    +          expression)          
    +
    +    Example::
    +        data_word = Word(alphas)
    +        label = data_word + FollowedBy(':')
    +        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
    +
    +        text = "shape: SQUARE posn: upper left color: BLACK"
    +        OneOrMore(attr_expr).parseString(text).pprint()  # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
    +
    +        # use stopOn attribute for OneOrMore to avoid reading label string as part of the data
    +        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
    +        OneOrMore(attr_expr).parseString(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']]
    +        
    +        # could also be written as
    +        (attr_expr * (1,)).parseString(text).pprint()
    +    """
    +
    +    def __str__( self ):
    +        if hasattr(self,"name"):
    +            return self.name
    +
    +        if self.strRepr is None:
    +            self.strRepr = "{" + _ustr(self.expr) + "}..."
    +
    +        return self.strRepr
    +
    +class ZeroOrMore(_MultipleMatch):
    +    """
    +    Optional repetition of zero or more of the given expression.
    +    
    +    Parameters:
    +     - expr - expression that must match zero or more times
    +     - stopOn - (default=C{None}) - expression for a terminating sentinel
    +          (only required if the sentinel would ordinarily match the repetition 
    +          expression)          
    +
    +    Example: similar to L{OneOrMore}
    +    """
    +    def __init__( self, expr, stopOn=None):
    +        super(ZeroOrMore,self).__init__(expr, stopOn=stopOn)
    +        self.mayReturnEmpty = True
    +        
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        try:
    +            return super(ZeroOrMore, self).parseImpl(instring, loc, doActions)
    +        except (ParseException,IndexError):
    +            return loc, []
    +
    +    def __str__( self ):
    +        if hasattr(self,"name"):
    +            return self.name
    +
    +        if self.strRepr is None:
    +            self.strRepr = "[" + _ustr(self.expr) + "]..."
    +
    +        return self.strRepr
    +
    +class _NullToken(object):
    +    def __bool__(self):
    +        return False
    +    __nonzero__ = __bool__
    +    def __str__(self):
    +        return ""
    +
    +_optionalNotMatched = _NullToken()
    +class Optional(ParseElementEnhance):
    +    """
    +    Optional matching of the given expression.
    +
    +    Parameters:
    +     - expr - expression that must match zero or more times
    +     - default (optional) - value to be returned if the optional expression is not found.
    +
    +    Example::
    +        # US postal code can be a 5-digit zip, plus optional 4-digit qualifier
    +        zip = Combine(Word(nums, exact=5) + Optional('-' + Word(nums, exact=4)))
    +        zip.runTests('''
    +            # traditional ZIP code
    +            12345
    +            
    +            # ZIP+4 form
    +            12101-0001
    +            
    +            # invalid ZIP
    +            98765-
    +            ''')
    +    prints::
    +        # traditional ZIP code
    +        12345
    +        ['12345']
    +
    +        # ZIP+4 form
    +        12101-0001
    +        ['12101-0001']
    +
    +        # invalid ZIP
    +        98765-
    +             ^
    +        FAIL: Expected end of text (at char 5), (line:1, col:6)
    +    """
    +    def __init__( self, expr, default=_optionalNotMatched ):
    +        super(Optional,self).__init__( expr, savelist=False )
    +        self.saveAsList = self.expr.saveAsList
    +        self.defaultValue = default
    +        self.mayReturnEmpty = True
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        try:
    +            loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False )
    +        except (ParseException,IndexError):
    +            if self.defaultValue is not _optionalNotMatched:
    +                if self.expr.resultsName:
    +                    tokens = ParseResults([ self.defaultValue ])
    +                    tokens[self.expr.resultsName] = self.defaultValue
    +                else:
    +                    tokens = [ self.defaultValue ]
    +            else:
    +                tokens = []
    +        return loc, tokens
    +
    +    def __str__( self ):
    +        if hasattr(self,"name"):
    +            return self.name
    +
    +        if self.strRepr is None:
    +            self.strRepr = "[" + _ustr(self.expr) + "]"
    +
    +        return self.strRepr
    +
    +class SkipTo(ParseElementEnhance):
    +    """
    +    Token for skipping over all undefined text until the matched expression is found.
    +
    +    Parameters:
    +     - expr - target expression marking the end of the data to be skipped
    +     - include - (default=C{False}) if True, the target expression is also parsed 
    +          (the skipped text and target expression are returned as a 2-element list).
    +     - ignore - (default=C{None}) used to define grammars (typically quoted strings and 
    +          comments) that might contain false matches to the target expression
    +     - failOn - (default=C{None}) define expressions that are not allowed to be 
    +          included in the skipped test; if found before the target expression is found, 
    +          the SkipTo is not a match
    +
    +    Example::
    +        report = '''
    +            Outstanding Issues Report - 1 Jan 2000
    +
    +               # | Severity | Description                               |  Days Open
    +            -----+----------+-------------------------------------------+-----------
    +             101 | Critical | Intermittent system crash                 |          6
    +              94 | Cosmetic | Spelling error on Login ('log|n')         |         14
    +              79 | Minor    | System slow when running too many reports |         47
    +            '''
    +        integer = Word(nums)
    +        SEP = Suppress('|')
    +        # use SkipTo to simply match everything up until the next SEP
    +        # - ignore quoted strings, so that a '|' character inside a quoted string does not match
    +        # - parse action will call token.strip() for each matched token, i.e., the description body
    +        string_data = SkipTo(SEP, ignore=quotedString)
    +        string_data.setParseAction(tokenMap(str.strip))
    +        ticket_expr = (integer("issue_num") + SEP 
    +                      + string_data("sev") + SEP 
    +                      + string_data("desc") + SEP 
    +                      + integer("days_open"))
    +        
    +        for tkt in ticket_expr.searchString(report):
    +            print tkt.dump()
    +    prints::
    +        ['101', 'Critical', 'Intermittent system crash', '6']
    +        - days_open: 6
    +        - desc: Intermittent system crash
    +        - issue_num: 101
    +        - sev: Critical
    +        ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14']
    +        - days_open: 14
    +        - desc: Spelling error on Login ('log|n')
    +        - issue_num: 94
    +        - sev: Cosmetic
    +        ['79', 'Minor', 'System slow when running too many reports', '47']
    +        - days_open: 47
    +        - desc: System slow when running too many reports
    +        - issue_num: 79
    +        - sev: Minor
    +    """
    +    def __init__( self, other, include=False, ignore=None, failOn=None ):
    +        super( SkipTo, self ).__init__( other )
    +        self.ignoreExpr = ignore
    +        self.mayReturnEmpty = True
    +        self.mayIndexError = False
    +        self.includeMatch = include
    +        self.asList = False
    +        if isinstance(failOn, basestring):
    +            self.failOn = ParserElement._literalStringClass(failOn)
    +        else:
    +            self.failOn = failOn
    +        self.errmsg = "No match found for "+_ustr(self.expr)
    +
    +    def parseImpl( self, instring, loc, doActions=True ):
    +        startloc = loc
    +        instrlen = len(instring)
    +        expr = self.expr
    +        expr_parse = self.expr._parse
    +        self_failOn_canParseNext = self.failOn.canParseNext if self.failOn is not None else None
    +        self_ignoreExpr_tryParse = self.ignoreExpr.tryParse if self.ignoreExpr is not None else None
    +        
    +        tmploc = loc
    +        while tmploc <= instrlen:
    +            if self_failOn_canParseNext is not None:
    +                # break if failOn expression matches
    +                if self_failOn_canParseNext(instring, tmploc):
    +                    break
    +                    
    +            if self_ignoreExpr_tryParse is not None:
    +                # advance past ignore expressions
    +                while 1:
    +                    try:
    +                        tmploc = self_ignoreExpr_tryParse(instring, tmploc)
    +                    except ParseBaseException:
    +                        break
    +            
    +            try:
    +                expr_parse(instring, tmploc, doActions=False, callPreParse=False)
    +            except (ParseException, IndexError):
    +                # no match, advance loc in string
    +                tmploc += 1
    +            else:
    +                # matched skipto expr, done
    +                break
    +
    +        else:
    +            # ran off the end of the input string without matching skipto expr, fail
    +            raise ParseException(instring, loc, self.errmsg, self)
    +
    +        # build up return values
    +        loc = tmploc
    +        skiptext = instring[startloc:loc]
    +        skipresult = ParseResults(skiptext)
    +        
    +        if self.includeMatch:
    +            loc, mat = expr_parse(instring,loc,doActions,callPreParse=False)
    +            skipresult += mat
    +
    +        return loc, skipresult
    +
    +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 C{Forward} variable using the '<<' operator.
    +
    +    Note: take care when assigning to C{Forward} not to overlook precedence of operators.
    +    Specifically, '|' has a lower precedence than '<<', so that::
    +        fwdExpr << a | b | c
    +    will actually be evaluated as::
    +        (fwdExpr << a) | b | c
    +    thereby leaving b and c out as parseable alternatives.  It is recommended that you
    +    explicitly group the values inserted into the C{Forward}::
    +        fwdExpr << (a | b | c)
    +    Converting to use the '<<=' operator instead will avoid this problem.
    +
    +    See L{ParseResults.pprint} for an example of a recursive parser created using
    +    C{Forward}.
    +    """
    +    def __init__( self, other=None ):
    +        super(Forward,self).__init__( other, savelist=False )
    +
    +    def __lshift__( self, other ):
    +        if isinstance( other, basestring ):
    +            other = ParserElement._literalStringClass(other)
    +        self.expr = other
    +        self.strRepr = None
    +        self.mayIndexError = self.expr.mayIndexError
    +        self.mayReturnEmpty = self.expr.mayReturnEmpty
    +        self.setWhitespaceChars( self.expr.whiteChars )
    +        self.skipWhitespace = self.expr.skipWhitespace
    +        self.saveAsList = self.expr.saveAsList
    +        self.ignoreExprs.extend(self.expr.ignoreExprs)
    +        return self
    +        
    +    def __ilshift__(self, other):
    +        return self << other
    +    
    +    def leaveWhitespace( self ):
    +        self.skipWhitespace = False
    +        return self
    +
    +    def streamline( self ):
    +        if not self.streamlined:
    +            self.streamlined = True
    +            if self.expr is not None:
    +                self.expr.streamline()
    +        return self
    +
    +    def validate( self, validateTrace=[] ):
    +        if self not in validateTrace:
    +            tmp = validateTrace[:]+[self]
    +            if self.expr is not None:
    +                self.expr.validate(tmp)
    +        self.checkRecursion([])
    +
    +    def __str__( self ):
    +        if hasattr(self,"name"):
    +            return self.name
    +        return self.__class__.__name__ + ": ..."
    +
    +        # stubbed out for now - creates awful memory and perf issues
    +        self._revertClass = self.__class__
    +        self.__class__ = _ForwardNoRecurse
    +        try:
    +            if self.expr is not None:
    +                retString = _ustr(self.expr)
    +            else:
    +                retString = "None"
    +        finally:
    +            self.__class__ = self._revertClass
    +        return self.__class__.__name__ + ": " + retString
    +
    +    def copy(self):
    +        if self.expr is not None:
    +            return super(Forward,self).copy()
    +        else:
    +            ret = Forward()
    +            ret <<= self
    +            return ret
    +
    +class _ForwardNoRecurse(Forward):
    +    def __str__( self ):
    +        return "..."
    +
    +class TokenConverter(ParseElementEnhance):
    +    """
    +    Abstract subclass of C{ParseExpression}, for converting parsed results.
    +    """
    +    def __init__( self, expr, savelist=False ):
    +        super(TokenConverter,self).__init__( expr )#, savelist )
    +        self.saveAsList = False
    +
    +class Combine(TokenConverter):
    +    """
    +    Converter to concatenate all matching tokens to a single string.
    +    By default, the matching patterns must also be contiguous in the input string;
    +    this can be disabled by specifying C{'adjacent=False'} in the constructor.
    +
    +    Example::
    +        real = Word(nums) + '.' + Word(nums)
    +        print(real.parseString('3.1416')) # -> ['3', '.', '1416']
    +        # will also erroneously match the following
    +        print(real.parseString('3. 1416')) # -> ['3', '.', '1416']
    +
    +        real = Combine(Word(nums) + '.' + Word(nums))
    +        print(real.parseString('3.1416')) # -> ['3.1416']
    +        # no match when there are internal spaces
    +        print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...)
    +    """
    +    def __init__( self, expr, joinString="", adjacent=True ):
    +        super(Combine,self).__init__( expr )
    +        # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself
    +        if adjacent:
    +            self.leaveWhitespace()
    +        self.adjacent = adjacent
    +        self.skipWhitespace = True
    +        self.joinString = joinString
    +        self.callPreparse = True
    +
    +    def ignore( self, other ):
    +        if self.adjacent:
    +            ParserElement.ignore(self, other)
    +        else:
    +            super( Combine, self).ignore( other )
    +        return self
    +
    +    def postParse( self, instring, loc, tokenlist ):
    +        retToks = tokenlist.copy()
    +        del retToks[:]
    +        retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults)
    +
    +        if self.resultsName and retToks.haskeys():
    +            return [ retToks ]
    +        else:
    +            return retToks
    +
    +class Group(TokenConverter):
    +    """
    +    Converter to return the matched tokens as a list - useful for returning tokens of C{L{ZeroOrMore}} and C{L{OneOrMore}} expressions.
    +
    +    Example::
    +        ident = Word(alphas)
    +        num = Word(nums)
    +        term = ident | num
    +        func = ident + Optional(delimitedList(term))
    +        print(func.parseString("fn a,b,100"))  # -> ['fn', 'a', 'b', '100']
    +
    +        func = ident + Group(Optional(delimitedList(term)))
    +        print(func.parseString("fn a,b,100"))  # -> ['fn', ['a', 'b', '100']]
    +    """
    +    def __init__( self, expr ):
    +        super(Group,self).__init__( expr )
    +        self.saveAsList = True
    +
    +    def postParse( self, instring, loc, tokenlist ):
    +        return [ tokenlist ]
    +
    +class Dict(TokenConverter):
    +    """
    +    Converter to return a repetitive expression as a list, but also as a dictionary.
    +    Each element can also be referenced using the first token in the expression as its key.
    +    Useful for tabular report scraping when the first column can be used as a item key.
    +
    +    Example::
    +        data_word = Word(alphas)
    +        label = data_word + FollowedBy(':')
    +        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
    +
    +        text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
    +        attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
    +        
    +        # print attributes as plain groups
    +        print(OneOrMore(attr_expr).parseString(text).dump())
    +        
    +        # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names
    +        result = Dict(OneOrMore(Group(attr_expr))).parseString(text)
    +        print(result.dump())
    +        
    +        # access named fields as dict entries, or output as dict
    +        print(result['shape'])        
    +        print(result.asDict())
    +    prints::
    +        ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap']
    +
    +        [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
    +        - color: light blue
    +        - posn: upper left
    +        - shape: SQUARE
    +        - texture: burlap
    +        SQUARE
    +        {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'}
    +    See more examples at L{ParseResults} of accessing fields by results name.
    +    """
    +    def __init__( self, expr ):
    +        super(Dict,self).__init__( expr )
    +        self.saveAsList = True
    +
    +    def postParse( self, instring, loc, tokenlist ):
    +        for i,tok in enumerate(tokenlist):
    +            if len(tok) == 0:
    +                continue
    +            ikey = tok[0]
    +            if isinstance(ikey,int):
    +                ikey = _ustr(tok[0]).strip()
    +            if len(tok)==1:
    +                tokenlist[ikey] = _ParseResultsWithOffset("",i)
    +            elif len(tok)==2 and not isinstance(tok[1],ParseResults):
    +                tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i)
    +            else:
    +                dictvalue = tok.copy() #ParseResults(i)
    +                del dictvalue[0]
    +                if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.haskeys()):
    +                    tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i)
    +                else:
    +                    tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i)
    +
    +        if self.resultsName:
    +            return [ tokenlist ]
    +        else:
    +            return tokenlist
    +
    +
    +class Suppress(TokenConverter):
    +    """
    +    Converter for ignoring the results of a parsed expression.
    +
    +    Example::
    +        source = "a, b, c,d"
    +        wd = Word(alphas)
    +        wd_list1 = wd + ZeroOrMore(',' + wd)
    +        print(wd_list1.parseString(source))
    +
    +        # 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 + ZeroOrMore(Suppress(',') + wd)
    +        print(wd_list2.parseString(source))
    +    prints::
    +        ['a', ',', 'b', ',', 'c', ',', 'd']
    +        ['a', 'b', 'c', 'd']
    +    (See also L{delimitedList}.)
    +    """
    +    def postParse( self, instring, loc, tokenlist ):
    +        return []
    +
    +    def suppress( self ):
    +        return self
    +
    +
    +class OnlyOnce(object):
    +    """
    +    Wrapper for parse actions, to ensure they are only called once.
    +    """
    +    def __init__(self, methodCall):
    +        self.callable = _trim_arity(methodCall)
    +        self.called = False
    +    def __call__(self,s,l,t):
    +        if not self.called:
    +            results = self.callable(s,l,t)
    +            self.called = True
    +            return results
    +        raise ParseException(s,l,"")
    +    def reset(self):
    +        self.called = False
    +
    +def traceParseAction(f):
    +    """
    +    Decorator for debugging parse actions. 
    +    
    +    When the parse action is called, this decorator will print C{">> entering I{method-name}(line:I{current_source_line}, I{parse_location}, I{matched_tokens})".}
    +    When the parse action completes, the decorator will print C{"<<"} followed by the returned value, or any exception that the parse action raised.
    +
    +    Example::
    +        wd = Word(alphas)
    +
    +        @traceParseAction
    +        def remove_duplicate_chars(tokens):
    +            return ''.join(sorted(set(''.join(tokens))))
    +
    +        wds = OneOrMore(wd).setParseAction(remove_duplicate_chars)
    +        print(wds.parseString("slkdjs sld sldd sdlf sdljf"))
    +    prints::
    +        >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {}))
    +        <3:
    +            thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc
    +        sys.stderr.write( ">>entering %s(line: '%s', %d, %r)\n" % (thisFunc,line(l,s),l,t) )
    +        try:
    +            ret = f(*paArgs)
    +        except Exception as exc:
    +            sys.stderr.write( "< ['aa', 'bb', 'cc']
    +        delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE']
    +    """
    +    dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..."
    +    if combine:
    +        return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName)
    +    else:
    +        return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName)
    +
    +def countedArray( expr, intExpr=None ):
    +    """
    +    Helper to define a counted list of expressions.
    +    This helper defines a pattern of the form::
    +        integer expr expr expr...
    +    where the leading integer tells how many expr expressions follow.
    +    The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed.
    +    
    +    If C{intExpr} is specified, it should be a pyparsing expression that produces an integer value.
    +
    +    Example::
    +        countedArray(Word(alphas)).parseString('2 ab cd ef')  # -> ['ab', 'cd']
    +
    +        # in this parser, the leading integer value is given in binary,
    +        # '10' indicating that 2 values are in the array
    +        binaryConstant = Word('01').setParseAction(lambda t: int(t[0], 2))
    +        countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef')  # -> ['ab', 'cd']
    +    """
    +    arrayExpr = Forward()
    +    def countFieldParseAction(s,l,t):
    +        n = t[0]
    +        arrayExpr << (n and Group(And([expr]*n)) or Group(empty))
    +        return []
    +    if intExpr is None:
    +        intExpr = Word(nums).setParseAction(lambda t:int(t[0]))
    +    else:
    +        intExpr = intExpr.copy()
    +    intExpr.setName("arrayLen")
    +    intExpr.addParseAction(countFieldParseAction, callDuringTry=True)
    +    return ( intExpr + arrayExpr ).setName('(len) ' + _ustr(expr) + '...')
    +
    +def _flatten(L):
    +    ret = []
    +    for i in L:
    +        if isinstance(i,list):
    +            ret.extend(_flatten(i))
    +        else:
    +            ret.append(i)
    +    return ret
    +
    +def matchPreviousLiteral(expr):
    +    """
    +    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::
    +        first = Word(nums)
    +        second = matchPreviousLiteral(first)
    +        matchExpr = first + ":" + second
    +    will match C{"1:1"}, but not C{"1:2"}.  Because this matches a
    +    previous literal, will also match the leading C{"1:1"} in C{"1:10"}.
    +    If this is not desired, use C{matchPreviousExpr}.
    +    Do I{not} use with packrat parsing enabled.
    +    """
    +    rep = Forward()
    +    def copyTokenToRepeater(s,l,t):
    +        if t:
    +            if len(t) == 1:
    +                rep << t[0]
    +            else:
    +                # flatten t tokens
    +                tflat = _flatten(t.asList())
    +                rep << And(Literal(tt) for tt in tflat)
    +        else:
    +            rep << Empty()
    +    expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
    +    rep.setName('(prev) ' + _ustr(expr))
    +    return rep
    +
    +def matchPreviousExpr(expr):
    +    """
    +    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::
    +        first = Word(nums)
    +        second = matchPreviousExpr(first)
    +        matchExpr = first + ":" + second
    +    will match C{"1:1"}, but not C{"1:2"}.  Because this matches by
    +    expressions, will I{not} match the leading C{"1:1"} in C{"1:10"};
    +    the expressions are evaluated first, and then compared, so
    +    C{"1"} is compared with C{"10"}.
    +    Do I{not} use with packrat parsing enabled.
    +    """
    +    rep = Forward()
    +    e2 = expr.copy()
    +    rep <<= e2
    +    def copyTokenToRepeater(s,l,t):
    +        matchTokens = _flatten(t.asList())
    +        def mustMatchTheseTokens(s,l,t):
    +            theseTokens = _flatten(t.asList())
    +            if  theseTokens != matchTokens:
    +                raise ParseException("",0,"")
    +        rep.setParseAction( mustMatchTheseTokens, callDuringTry=True )
    +    expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
    +    rep.setName('(prev) ' + _ustr(expr))
    +    return rep
    +
    +def _escapeRegexRangeChars(s):
    +    #~  escape these chars: ^-]
    +    for c in r"\^-]":
    +        s = s.replace(c,_bslash+c)
    +    s = s.replace("\n",r"\n")
    +    s = s.replace("\t",r"\t")
    +    return _ustr(s)
    +
    +def oneOf( strs, caseless=False, useRegex=True ):
    +    """
    +    Helper to quickly define a set of alternative Literals, and makes sure to do
    +    longest-first testing when there is a conflict, regardless of the input order,
    +    but returns a C{L{MatchFirst}} for best performance.
    +
    +    Parameters:
    +     - strs - a string of space-delimited literals, or a collection of string literals
    +     - caseless - (default=C{False}) - treat all literals as caseless
    +     - useRegex - (default=C{True}) - as an optimization, will generate a Regex
    +          object; otherwise, will generate a C{MatchFirst} object (if C{caseless=True}, or
    +          if creating a C{Regex} raises an exception)
    +
    +    Example::
    +        comp_oper = oneOf("< = > <= >= !=")
    +        var = Word(alphas)
    +        number = Word(nums)
    +        term = var | number
    +        comparison_expr = term + comp_oper + term
    +        print(comparison_expr.searchString("B = 12  AA=23 B<=AA AA>12"))
    +    prints::
    +        [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']]
    +    """
    +    if caseless:
    +        isequal = ( lambda a,b: a.upper() == b.upper() )
    +        masks = ( lambda a,b: b.upper().startswith(a.upper()) )
    +        parseElementClass = CaselessLiteral
    +    else:
    +        isequal = ( lambda a,b: a == b )
    +        masks = ( lambda a,b: b.startswith(a) )
    +        parseElementClass = Literal
    +
    +    symbols = []
    +    if isinstance(strs,basestring):
    +        symbols = strs.split()
    +    elif isinstance(strs, Iterable):
    +        symbols = list(strs)
    +    else:
    +        warnings.warn("Invalid argument to oneOf, expected string or iterable",
    +                SyntaxWarning, stacklevel=2)
    +    if not symbols:
    +        return NoMatch()
    +
    +    i = 0
    +    while i < len(symbols)-1:
    +        cur = symbols[i]
    +        for j,other in enumerate(symbols[i+1:]):
    +            if ( isequal(other, cur) ):
    +                del symbols[i+j+1]
    +                break
    +            elif ( masks(cur, other) ):
    +                del symbols[i+j+1]
    +                symbols.insert(i,other)
    +                cur = other
    +                break
    +        else:
    +            i += 1
    +
    +    if not caseless and useRegex:
    +        #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] ))
    +        try:
    +            if len(symbols)==len("".join(symbols)):
    +                return Regex( "[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols) ).setName(' | '.join(symbols))
    +            else:
    +                return Regex( "|".join(re.escape(sym) for sym in symbols) ).setName(' | '.join(symbols))
    +        except Exception:
    +            warnings.warn("Exception creating Regex for oneOf, building MatchFirst",
    +                    SyntaxWarning, stacklevel=2)
    +
    +
    +    # last resort, just use MatchFirst
    +    return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols))
    +
    +def dictOf( key, value ):
    +    """
    +    Helper to easily and clearly define a dictionary by specifying the respective patterns
    +    for the key and value.  Takes care of defining the C{L{Dict}}, C{L{ZeroOrMore}}, and C{L{Group}} tokens
    +    in the proper order.  The key pattern can include delimiting markers or punctuation,
    +    as long as they are suppressed, thereby leaving the significant key text.  The value
    +    pattern can include named results, so that the C{Dict} results can include named token
    +    fields.
    +
    +    Example::
    +        text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
    +        attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
    +        print(OneOrMore(attr_expr).parseString(text).dump())
    +        
    +        attr_label = label
    +        attr_value = Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)
    +
    +        # similar to Dict, but simpler call format
    +        result = dictOf(attr_label, attr_value).parseString(text)
    +        print(result.dump())
    +        print(result['shape'])
    +        print(result.shape)  # object attribute access works too
    +        print(result.asDict())
    +    prints::
    +        [['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'}
    +    """
    +    return Dict( ZeroOrMore( Group ( key + value ) ) )
    +
    +def originalTextFor(expr, asString=True):
    +    """
    +    Helper to return the original, untokenized text for a given expression.  Useful to
    +    restore the parsed fields of an HTML start tag into the raw tag text itself, or to
    +    revert separate tokens with intervening whitespace back to the original matching
    +    input text. By default, returns astring containing the original parsed text.  
    +       
    +    If the optional C{asString} argument is passed as C{False}, then the return value is a 
    +    C{L{ParseResults}} containing any results names that were originally matched, and a 
    +    single token containing the original matched text from the input string.  So if 
    +    the expression passed to C{L{originalTextFor}} contains expressions with defined
    +    results names, you must set C{asString} to C{False} if you want to preserve those
    +    results name values.
    +
    +    Example::
    +        src = "this is test  bold text  normal text "
    +        for tag in ("b","i"):
    +            opener,closer = makeHTMLTags(tag)
    +            patt = originalTextFor(opener + SkipTo(closer) + closer)
    +            print(patt.searchString(src)[0])
    +    prints::
    +        [' bold text ']
    +        ['text']
    +    """
    +    locMarker = Empty().setParseAction(lambda s,loc,t: loc)
    +    endlocMarker = locMarker.copy()
    +    endlocMarker.callPreparse = False
    +    matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end")
    +    if asString:
    +        extractText = lambda s,l,t: s[t._original_start:t._original_end]
    +    else:
    +        def extractText(s,l,t):
    +            t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]]
    +    matchExpr.setParseAction(extractText)
    +    matchExpr.ignoreExprs = expr.ignoreExprs
    +    return matchExpr
    +
    +def ungroup(expr): 
    +    """
    +    Helper to undo pyparsing's default grouping of And expressions, even
    +    if all but one are non-empty.
    +    """
    +    return TokenConverter(expr).setParseAction(lambda t:t[0])
    +
    +def locatedExpr(expr):
    +    """
    +    Helper to decorate a returned token with its starting and ending locations in the input string.
    +    This helper adds the following results names:
    +     - locn_start = location where matched expression begins
    +     - locn_end = location where matched expression ends
    +     - value = the actual parsed results
    +
    +    Be careful if the input text contains C{} characters, you may want to call
    +    C{L{ParserElement.parseWithTabs}}
    +
    +    Example::
    +        wd = Word(alphas)
    +        for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"):
    +            print(match)
    +    prints::
    +        [[0, 'ljsdf', 5]]
    +        [[8, 'lksdjjf', 15]]
    +        [[18, 'lkkjj', 23]]
    +    """
    +    locator = Empty().setParseAction(lambda s,l,t: l)
    +    return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end"))
    +
    +
    +# convenience constants for positional expressions
    +empty       = Empty().setName("empty")
    +lineStart   = LineStart().setName("lineStart")
    +lineEnd     = LineEnd().setName("lineEnd")
    +stringStart = StringStart().setName("stringStart")
    +stringEnd   = StringEnd().setName("stringEnd")
    +
    +_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1])
    +_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16)))
    +_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8)))
    +_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1)
    +_charRange = Group(_singleChar + Suppress("-") + _singleChar)
    +_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]"
    +
    +def srange(s):
    +    r"""
    +    Helper to easily define string ranges for use in Word construction.  Borrows
    +    syntax from regexp '[]' string range definitions::
    +        srange("[0-9]")   -> "0123456789"
    +        srange("[a-z]")   -> "abcdefghijklmnopqrstuvwxyz"
    +        srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_"
    +    The input string must be enclosed in []'s, and the returned string is the expanded
    +    character set joined into a single string.
    +    The values enclosed in the []'s may be:
    +     - a single character
    +     - an escaped character with a leading backslash (such as C{\-} or C{\]})
    +     - an escaped hex character with a leading C{'\x'} (C{\x21}, which is a C{'!'} character) 
    +         (C{\0x##} is also supported for backwards compatibility) 
    +     - an escaped octal character with a leading C{'\0'} (C{\041}, which is a C{'!'} character)
    +     - a range of any of the above, separated by a dash (C{'a-z'}, etc.)
    +     - any combination of the above (C{'aeiouy'}, C{'a-zA-Z0-9_$'}, etc.)
    +    """
    +    _expanded = lambda p: p if not isinstance(p,ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]),ord(p[1])+1))
    +    try:
    +        return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body)
    +    except Exception:
    +        return ""
    +
    +def matchOnlyAtCol(n):
    +    """
    +    Helper method for defining parse actions that require matching at a specific
    +    column in the input text.
    +    """
    +    def verifyCol(strg,locn,toks):
    +        if col(locn,strg) != n:
    +            raise ParseException(strg,locn,"matched token not at column %d" % n)
    +    return verifyCol
    +
    +def replaceWith(replStr):
    +    """
    +    Helper method for common parse actions that simply return a literal value.  Especially
    +    useful when used with C{L{transformString}()}.
    +
    +    Example::
    +        num = Word(nums).setParseAction(lambda toks: int(toks[0]))
    +        na = oneOf("N/A NA").setParseAction(replaceWith(math.nan))
    +        term = na | num
    +        
    +        OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234]
    +    """
    +    return lambda s,l,t: [replStr]
    +
    +def removeQuotes(s,l,t):
    +    """
    +    Helper parse action for removing quotation marks from parsed quoted strings.
    +
    +    Example::
    +        # by default, quotation marks are included in parsed results
    +        quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"]
    +
    +        # use removeQuotes to strip quotation marks from parsed results
    +        quotedString.setParseAction(removeQuotes)
    +        quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"]
    +    """
    +    return t[0][1:-1]
    +
    +def tokenMap(func, *args):
    +    """
    +    Helper to define a parse action by mapping a function to all elements of a ParseResults list.If any additional 
    +    args are passed, they are forwarded to the given function as additional arguments after
    +    the token, as in C{hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))}, which will convert the
    +    parsed data to an integer using base 16.
    +
    +    Example (compare the last to example in L{ParserElement.transformString}::
    +        hex_ints = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16))
    +        hex_ints.runTests('''
    +            00 11 22 aa FF 0a 0d 1a
    +            ''')
    +        
    +        upperword = Word(alphas).setParseAction(tokenMap(str.upper))
    +        OneOrMore(upperword).runTests('''
    +            my kingdom for a horse
    +            ''')
    +
    +        wd = Word(alphas).setParseAction(tokenMap(str.title))
    +        OneOrMore(wd).setParseAction(' '.join).runTests('''
    +            now is the winter of our discontent made glorious summer by this sun of york
    +            ''')
    +    prints::
    +        00 11 22 aa FF 0a 0d 1a
    +        [0, 17, 34, 170, 255, 10, 13, 26]
    +
    +        my kingdom for a horse
    +        ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE']
    +
    +        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']
    +    """
    +    def pa(s,l,t):
    +        return [func(tokn, *args) for tokn in t]
    +
    +    try:
    +        func_name = getattr(func, '__name__', 
    +                            getattr(func, '__class__').__name__)
    +    except Exception:
    +        func_name = str(func)
    +    pa.__name__ = func_name
    +
    +    return pa
    +
    +upcaseTokens = tokenMap(lambda t: _ustr(t).upper())
    +"""(Deprecated) Helper parse action to convert tokens to upper case. Deprecated in favor of L{pyparsing_common.upcaseTokens}"""
    +
    +downcaseTokens = tokenMap(lambda t: _ustr(t).lower())
    +"""(Deprecated) Helper parse action to convert tokens to lower case. Deprecated in favor of L{pyparsing_common.downcaseTokens}"""
    +    
    +def _makeTags(tagStr, xml):
    +    """Internal helper to construct opening and closing tag expressions, given a tag name"""
    +    if isinstance(tagStr,basestring):
    +        resname = tagStr
    +        tagStr = Keyword(tagStr, caseless=not xml)
    +    else:
    +        resname = tagStr.name
    +
    +    tagAttrName = Word(alphas,alphanums+"_-:")
    +    if (xml):
    +        tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes )
    +        openTag = Suppress("<") + tagStr("tag") + \
    +                Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \
    +                Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">")
    +    else:
    +        printablesLessRAbrack = "".join(c for c in printables if c not in ">")
    +        tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack)
    +        openTag = Suppress("<") + tagStr("tag") + \
    +                Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \
    +                Optional( Suppress("=") + tagAttrValue ) ))) + \
    +                Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">")
    +    closeTag = Combine(_L("")
    +
    +    openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % resname)
    +    closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("" % resname)
    +    openTag.tag = resname
    +    closeTag.tag = resname
    +    return openTag, closeTag
    +
    +def makeHTMLTags(tagStr):
    +    """
    +    Helper to construct opening and closing tag expressions for HTML, given a tag name. Matches
    +    tags in either upper or lower case, attributes with namespaces and with quoted or unquoted values.
    +
    +    Example::
    +        text = 'More info at the pyparsing wiki page'
    +        # makeHTMLTags returns pyparsing expressions for the opening and closing tags as a 2-tuple
    +        a,a_end = makeHTMLTags("A")
    +        link_expr = a + SkipTo(a_end)("link_text") + a_end
    +        
    +        for link in link_expr.searchString(text):
    +            # attributes in the  tag (like "href" shown here) are also accessible as named results
    +            print(link.link_text, '->', link.href)
    +    prints::
    +        pyparsing -> http://pyparsing.wikispaces.com
    +    """
    +    return _makeTags( tagStr, False )
    +
    +def makeXMLTags(tagStr):
    +    """
    +    Helper to construct opening and closing tag expressions for XML, given a tag name. Matches
    +    tags only in the given upper/lower case.
    +
    +    Example: similar to L{makeHTMLTags}
    +    """
    +    return _makeTags( tagStr, True )
    +
    +def withAttribute(*args,**attrDict):
    +    """
    +    Helper to create a validating parse action to be used with start tags created
    +    with C{L{makeXMLTags}} or C{L{makeHTMLTags}}. Use C{withAttribute} to qualify a starting tag
    +    with a required attribute value, to avoid false matches on common tags such as
    +    C{} or C{
    }. + + Call C{withAttribute} with a series of attribute names and values. Specify the list + of filter attributes names and values as: + - keyword arguments, as in C{(align="right")}, or + - as an explicit dict with C{**} operator, when an attribute name is also a Python + reserved word, as in C{**{"class":"Customer", "align":"right"}} + - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) + For attribute names with a namespace prefix, you must use the second form. Attribute + names are matched insensitive to upper/lower case. + + If just testing for C{class} (with or without a namespace), use C{L{withClass}}. + + To verify that the attribute exists, but without specifying a value, pass + C{withAttribute.ANY_VALUE} as the value. + + Example:: + html = ''' +
    + Some text +
    1 4 0 1 0
    +
    1,3 2,3 1,1
    +
    this has no type
    +
    + + ''' + div,div_end = makeHTMLTags("div") + + # only match div tag having a type attribute with value "grid" + div_grid = div().setParseAction(withAttribute(type="grid")) + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + # construct a match with any div tag having a type attribute, regardless of the value + div_any_type = div().setParseAction(withAttribute(type=withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + if args: + attrs = args[:] + else: + attrs = attrDict.items() + attrs = [(k,v) for k,v in attrs] + def pa(s,l,tokens): + for attrName,attrValue in attrs: + if attrName not in tokens: + raise ParseException(s,l,"no matching attribute " + attrName) + if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + (attrName, tokens[attrName], attrValue)) + return pa +withAttribute.ANY_VALUE = object() + +def withClass(classname, namespace=''): + """ + Simplified version of C{L{withAttribute}} when matching on a div class - made + difficult because C{class} is a reserved word in Python. + + Example:: + html = ''' +
    + Some text +
    1 4 0 1 0
    +
    1,3 2,3 1,1
    +
    this <div> has no class
    +
    + + ''' + div,div_end = makeHTMLTags("div") + div_grid = div().setParseAction(withClass("grid")) + + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + div_any_type = div().setParseAction(withClass(withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + classattr = "%s:class" % namespace if namespace else "class" + return withAttribute(**{classattr : classname}) + +opAssoc = _Constants() +opAssoc.LEFT = object() +opAssoc.RIGHT = object() + +def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): + """ + Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary or + binary, left- or right-associative. Parse actions can also be attached + to operator expressions. The generated parser will also recognize the use + of parentheses to override operator precedences (see example below). + + Note: if you define a deep operator list, you may see performance issues + when using infixNotation. See L{ParserElement.enablePackrat} for a + mechanism to potentially improve your parser performance. + + Parameters: + - baseExpr - expression representing the most basic element for the nested + - opList - list of tuples, one for each operator precedence level in the + expression grammar; each tuple is of the form + (opExpr, numTerms, rightLeftAssoc, parseAction), where: + - opExpr is the pyparsing expression for the operator; + may also be a string, which will be converted to a Literal; + if numTerms is 3, opExpr is a tuple of two expressions, for the + two operators separating the 3 terms + - numTerms is the number of terms for this operator (must + be 1, 2, or 3) + - rightLeftAssoc is the indicator whether the operator is + right or left associative, using the pyparsing-defined + constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. + - parseAction 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 C{setParseAction(*fn)} (L{ParserElement.setParseAction}) + - lpar - expression for matching left-parentheses (default=C{Suppress('(')}) + - rpar - expression for matching right-parentheses (default=C{Suppress(')')}) + + Example:: + # simple example of four-function arithmetic with ints and variable names + integer = pyparsing_common.signed_integer + varname = pyparsing_common.identifier + + arith_expr = infixNotation(integer | varname, + [ + ('-', 1, opAssoc.RIGHT), + (oneOf('* /'), 2, opAssoc.LEFT), + (oneOf('+ -'), 2, opAssoc.LEFT), + ]) + + arith_expr.runTests(''' + 5+3*6 + (5+3)*6 + -2--11 + ''', fullDump=False) + prints:: + 5+3*6 + [[5, '+', [3, '*', 6]]] + + (5+3)*6 + [[[5, '+', 3], '*', 6]] + + -2--11 + [[['-', 2], '-', ['-', 11]]] + """ + ret = Forward() + lastExpr = baseExpr | ( lpar + ret + rpar ) + for i,operDef in enumerate(opList): + opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr + if arity == 3: + if opExpr is None or len(opExpr) != 2: + raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") + opExpr1, opExpr2 = opExpr + thisExpr = Forward().setName(termName) + if rightLeftAssoc == opAssoc.LEFT: + if arity == 1: + matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + else: + matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ + Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + elif rightLeftAssoc == opAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Optional): + opExpr = Optional(opExpr) + matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + else: + matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ + Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + else: + raise ValueError("operator must indicate right or left associativity") + if pa: + if isinstance(pa, (tuple, list)): + matchExpr.setParseAction(*pa) + else: + matchExpr.setParseAction(pa) + thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) + lastExpr = thisExpr + ret <<= lastExpr + return ret + +operatorPrecedence = infixNotation +"""(Deprecated) Former name of C{L{infixNotation}}, will be dropped in a future release.""" + +dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"').setName("string enclosed in double quotes") +sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("string enclosed in single quotes") +quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"'| + Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("quotedString using single or double quotes") +unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal") + +def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): + """ + Helper method for defining nested lists enclosed in opening and closing + delimiters ("(" and ")" are the default). + + Parameters: + - opener - opening character for a nested list (default=C{"("}); can also be a pyparsing expression + - closer - closing character for a nested list (default=C{")"}); can also be a pyparsing expression + - content - expression for items within the nested lists (default=C{None}) + - ignoreExpr - expression for ignoring opening and closing delimiters (default=C{quotedString}) + + If an expression is not provided for the content argument, the nested + expression will capture all whitespace-delimited content between delimiters + as a list of separate values. + + Use the C{ignoreExpr} argument to define expressions that may contain + opening or closing characters that should not be treated as opening + or closing characters for nesting, such as quotedString or a comment + expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. + The default is L{quotedString}, but if no expressions are to be ignored, + then pass C{None} for this argument. + + Example:: + data_type = oneOf("void int short long char float double") + decl_data_type = Combine(data_type + Optional(Word('*'))) + ident = Word(alphas+'_', alphanums+'_') + number = pyparsing_common.number + arg = Group(decl_data_type + ident) + LPAR,RPAR = map(Suppress, "()") + + code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment)) + + c_function = (decl_data_type("type") + + ident("name") + + LPAR + Optional(delimitedList(arg), [])("args") + RPAR + + code_body("body")) + c_function.ignore(cStyleComment) + + 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.searchString(source_code): + print("%(name)s (%(type)s) args: %(args)s" % func) + + prints:: + is_odd (int) args: [['int', 'x']] + dec_to_hex (int) args: [['char', 'hchar']] + """ + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener,basestring) and isinstance(closer,basestring): + if len(opener) == 1 and len(closer)==1: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t:t[0].strip())) + else: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + raise ValueError("opening and closing arguments must be strings if no content expression is given") + ret = Forward() + if ignoreExpr is not None: + ret <<= Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + else: + ret <<= Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) + ret.setName('nested %s%s expression' % (opener,closer)) + return ret + +def indentedBlock(blockStatementExpr, indentStack, indent=True): + """ + 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 + is repeated within the indented block + - 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 the + the current level; set to False for block of left-most statements + (default=C{True}) + + A valid block must contain at least one C{blockStatement}. + + Example:: + 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() + + identifier = Word(alphas, alphanums) + funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group( funcDecl + func_body ) + + rvalue = Forward() + funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << ( funcDef | assignment | identifier ) + + module_body = OneOrMore(stmt) + + parseTree = module_body.parseString(data) + parseTree.pprint() + prints:: + [['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 checkPeerIndent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseFatalException(s,l,"illegal nesting") + raise ParseException(s,l,"not a peer entry") + + def checkSubIndent(s,l,t): + curCol = col(l,s) + if curCol > indentStack[-1]: + indentStack.append( curCol ) + else: + raise ParseException(s,l,"not a subentry") + + def checkUnindent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): + raise ParseException(s,l,"not an unindent") + indentStack.pop() + + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') + PEER = Empty().setParseAction(checkPeerIndent).setName('') + UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') + if indent: + smExpr = Group( Optional(NL) + + #~ FollowedBy(blockStatementExpr) + + INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + else: + smExpr = Group( Optional(NL) + + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr.setName('indented block') + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:").setName('any tag')) +_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(),'><& "\'')) +commonHTMLEntity = Regex('&(?P' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity") +def replaceHTMLEntity(t): + """Helper parser action to replace common HTML entities with their special characters""" + return _htmlEntityMap.get(t.entity) + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/').setName("C style comment") +"Comment of the form C{/* ... */}" + +htmlComment = Regex(r"").setName("HTML comment") +"Comment of the form C{}" + +restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line") +dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment") +"Comment of the form C{// ... (to end of line)}" + +cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/'| dblSlashComment).setName("C++ style comment") +"Comment of either form C{L{cStyleComment}} or C{L{dblSlashComment}}" + +javaStyleComment = cppStyleComment +"Same as C{L{cppStyleComment}}" + +pythonStyleComment = Regex(r"#.*").setName("Python style comment") +"Comment of the form C{# ... (to end of line)}" + +_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + + Optional( Word(" \t") + + ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") +commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") +"""(Deprecated) Predefined expression of 1 or more printable words or quoted strings, separated by commas. + This expression is deprecated in favor of L{pyparsing_common.comma_separated_list}.""" + +# some other useful expressions - using lower-case class name since we are really using this as a namespace +class pyparsing_common: + """ + Here are some common low-level expressions that may be useful in jump-starting parser development: + - numeric forms (L{integers}, L{reals}, L{scientific notation}) + - common L{programming identifiers} + - network addresses (L{MAC}, L{IPv4}, L{IPv6}) + - ISO8601 L{dates} and L{datetime} + - L{UUID} + - L{comma-separated list} + Parse actions: + - C{L{convertToInteger}} + - C{L{convertToFloat}} + - C{L{convertToDate}} + - C{L{convertToDatetime}} + - C{L{stripHTMLTags}} + - C{L{upcaseTokens}} + - C{L{downcaseTokens}} + + Example:: + pyparsing_common.number.runTests(''' + # any int or real number, returned as the appropriate type + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.fnumber.runTests(''' + # any int or real number, returned as float + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.hex_integer.runTests(''' + # hex numbers + 100 + FF + ''') + + pyparsing_common.fraction.runTests(''' + # fractions + 1/2 + -3/4 + ''') + + pyparsing_common.mixed_integer.runTests(''' + # mixed fractions + 1 + 1/2 + -3/4 + 1-3/4 + ''') + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(''' + # uuid + 12345678-1234-5678-1234-567812345678 + ''') + prints:: + # any int or real number, returned as the appropriate type + 100 + [100] + + -100 + [-100] + + +100 + [100] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # any int or real number, returned as float + 100 + [100.0] + + -100 + [-100.0] + + +100 + [100.0] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # hex numbers + 100 + [256] + + FF + [255] + + # fractions + 1/2 + [0.5] + + -3/4 + [-0.75] + + # mixed fractions + 1 + [1] + + 1/2 + [0.5] + + -3/4 + [-0.75] + + 1-3/4 + [1.75] + + # uuid + 12345678-1234-5678-1234-567812345678 + [UUID('12345678-1234-5678-1234-567812345678')] + """ + + convertToInteger = tokenMap(int) + """ + Parse action for converting parsed integers to Python int + """ + + convertToFloat = tokenMap(float) + """ + Parse action for converting parsed numbers to Python float + """ + + integer = Word(nums).setName("integer").setParseAction(convertToInteger) + """expression that parses an unsigned integer, returns an int""" + + hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int,16)) + """expression that parses a hexadecimal integer, returns an int""" + + signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger) + """expression that parses an integer with optional leading sign, returns an int""" + + fraction = (signed_integer().setParseAction(convertToFloat) + '/' + signed_integer().setParseAction(convertToFloat)).setName("fraction") + """fractional expression of an integer divided by an integer, returns a float""" + fraction.addParseAction(lambda t: t[0]/t[-1]) + + mixed_integer = (fraction | signed_integer + Optional(Optional('-').suppress() + fraction)).setName("fraction or mixed integer-fraction") + """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" + mixed_integer.addParseAction(sum) + + real = Regex(r'[+-]?\d+\.\d*').setName("real number").setParseAction(convertToFloat) + """expression that parses a floating point number and returns a float""" + + sci_real = Regex(r'[+-]?\d+([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) + """expression that parses a floating point number with optional scientific notation and returns a float""" + + # streamlining this expression makes the docs nicer-looking + number = (sci_real | real | signed_integer).streamline() + """any numeric expression, returns the corresponding Python type""" + + fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat) + """any int or real number, returned as float""" + + identifier = Word(alphas+'_', alphanums+'_').setName("identifier") + """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" + + ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address") + "IPv4 address (C{0.0.0.0 - 255.255.255.255})" + + _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer") + _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part)*7).setName("full IPv6 address") + _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part)*(0,6)) + "::" + Optional(_ipv6_part + (':' + _ipv6_part)*(0,6))).setName("short IPv6 address") + _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8) + _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address") + ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address") + "IPv6 address (long, short, or mixed form)" + + mac_address = Regex(r'[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}').setName("MAC address") + "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)" + + @staticmethod + def convertToDate(fmt="%Y-%m-%d"): + """ + Helper to create a parse action for converting parsed date string to Python datetime.date + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%d"}) + + Example:: + date_expr = pyparsing_common.iso8601_date.copy() + date_expr.setParseAction(pyparsing_common.convertToDate()) + print(date_expr.parseString("1999-12-31")) + prints:: + [datetime.date(1999, 12, 31)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt).date() + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + @staticmethod + def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"): + """ + Helper to create a parse action for converting parsed datetime string to Python datetime.datetime + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%dT%H:%M:%S.%f"}) + + Example:: + dt_expr = pyparsing_common.iso8601_datetime.copy() + dt_expr.setParseAction(pyparsing_common.convertToDatetime()) + print(dt_expr.parseString("1999-12-31T23:59:59.999")) + prints:: + [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt) + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + iso8601_date = Regex(r'(?P\d{4})(?:-(?P\d\d)(?:-(?P\d\d))?)?').setName("ISO8601 date") + "ISO8601 date (C{yyyy-mm-dd})" + + iso8601_datetime = Regex(r'(?P\d{4})-(?P\d\d)-(?P\d\d)[T ](?P\d\d):(?P\d\d)(:(?P\d\d(\.\d*)?)?)?(?PZ|[+-]\d\d:?\d\d)?').setName("ISO8601 datetime") + "ISO8601 datetime (C{yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)}) - trailing seconds, milliseconds, and timezone optional; accepts separating C{'T'} or C{' '}" + + uuid = Regex(r'[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').setName("UUID") + "UUID (C{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx})" + + _html_stripper = anyOpenTag.suppress() | anyCloseTag.suppress() + @staticmethod + def stripHTMLTags(s, l, tokens): + """ + Parse action to remove HTML tags from web page HTML source + + Example:: + # strip HTML links from normal text + text = 'More info at the
    pyparsing wiki page' + td,td_end = makeHTMLTags("TD") + table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end + + print(table_text.parseString(text).body) # -> 'More info at the pyparsing wiki page' + """ + return pyparsing_common._html_stripper.transformString(tokens[0]) + + _commasepitem = Combine(OneOrMore(~Literal(",") + ~LineEnd() + Word(printables, excludeChars=',') + + Optional( White(" \t") ) ) ).streamline().setName("commaItem") + comma_separated_list = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("comma separated list") + """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" + + upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper())) + """Parse action to convert tokens to upper case.""" + + downcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).lower())) + """Parse action to convert tokens to lower case.""" + + +if __name__ == "__main__": + + selectToken = CaselessLiteral("select") + fromToken = CaselessLiteral("from") + + ident = Word(alphas, alphanums + "_$") + + columnName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + columnNameList = Group(delimitedList(columnName)).setName("columns") + columnSpec = ('*' | columnNameList) + + tableName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + tableNameList = Group(delimitedList(tableName)).setName("tables") + + simpleSQL = selectToken("command") + columnSpec("columns") + fromToken + tableNameList("tables") + + # demo runTests method, including embedded comments in test string + simpleSQL.runTests(""" + # '*' as column list and dotted table name + select * from SYS.XYZZY + + # caseless match on "SELECT", and casts back to "select" + SELECT * from XYZZY, ABC + + # list of column names, and mixed case SELECT keyword + Select AA,BB,CC from Sys.dual + + # multiple tables + Select A, B, C from Sys.dual, Table2 + + # invalid SELECT keyword - should fail + Xelect A, B, C from Sys.dual + + # incomplete command - should fail + Select + + # invalid column name - should fail + Select ^^^ frox Sys.dual + + """) + + pyparsing_common.number.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + # any int or real number, returned as float + pyparsing_common.fnumber.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + pyparsing_common.hex_integer.runTests(""" + 100 + FF + """) + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(""" + 12345678-1234-5678-1234-567812345678 + """) diff --git a/robot/lib/python3.8/site-packages/setuptools/_vendor/six.py b/robot/lib/python3.8/site-packages/setuptools/_vendor/six.py new file mode 100644 index 0000000000000000000000000000000000000000..190c0239cd7d7af82a6e0cbc8d68053fa2e3dfaf --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/_vendor/six.py @@ -0,0 +1,868 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +# Copyright (c) 2010-2015 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.10.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + unichr = chr + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + +if sys.version_info[:2] == (3, 2): + exec_("""def raise_from(value, from_value): + if from_value is None: + raise value + raise value from from_value +""") +elif sys.version_info[:2] > (3, 2): + exec_("""def raise_from(value, from_value): + raise value from from_value +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + def wrapper(f): + f = functools.wraps(wrapped, assigned, updated)(f) + f.__wrapped__ = wrapped + return f + return wrapper +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/robot/lib/python3.8/site-packages/setuptools/archive_util.py b/robot/lib/python3.8/site-packages/setuptools/archive_util.py new file mode 100644 index 0000000000000000000000000000000000000000..81436044d995ff430334a7ef324b08e616f4b7a7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/archive_util.py @@ -0,0 +1,173 @@ +"""Utilities for extracting common archive formats""" + +import zipfile +import tarfile +import os +import shutil +import posixpath +import contextlib +from distutils.errors import DistutilsError + +from pkg_resources import ensure_directory + +__all__ = [ + "unpack_archive", "unpack_zipfile", "unpack_tarfile", "default_filter", + "UnrecognizedFormat", "extraction_drivers", "unpack_directory", +] + + +class UnrecognizedFormat(DistutilsError): + """Couldn't recognize the archive type""" + + +def default_filter(src, dst): + """The default progress/filter callback; returns True for all files""" + return dst + + +def unpack_archive(filename, extract_dir, progress_filter=default_filter, + drivers=None): + """Unpack `filename` to `extract_dir`, or raise ``UnrecognizedFormat`` + + `progress_filter` is a function taking two arguments: a source path + internal to the archive ('/'-separated), and a filesystem path where it + will be extracted. The callback must return the desired extract path + (which may be the same as the one passed in), or else ``None`` to skip + that file or directory. The callback can thus be used to report on the + progress of the extraction, as well as to filter the items extracted or + alter their extraction paths. + + `drivers`, if supplied, must be a non-empty sequence of functions with the + same signature as this function (minus the `drivers` argument), that raise + ``UnrecognizedFormat`` if they do not support extracting the designated + archive type. The `drivers` are tried in sequence until one is found that + does not raise an error, or until all are exhausted (in which case + ``UnrecognizedFormat`` is raised). If you do not supply a sequence of + drivers, the module's ``extraction_drivers`` constant will be used, which + means that ``unpack_zipfile`` and ``unpack_tarfile`` will be tried, in that + order. + """ + for driver in drivers or extraction_drivers: + try: + driver(filename, extract_dir, progress_filter) + except UnrecognizedFormat: + continue + else: + return + else: + raise UnrecognizedFormat( + "Not a recognized archive type: %s" % filename + ) + + +def unpack_directory(filename, extract_dir, progress_filter=default_filter): + """"Unpack" a directory, using the same interface as for archives + + Raises ``UnrecognizedFormat`` if `filename` is not a directory + """ + if not os.path.isdir(filename): + raise UnrecognizedFormat("%s is not a directory" % filename) + + paths = { + filename: ('', extract_dir), + } + for base, dirs, files in os.walk(filename): + src, dst = paths[base] + for d in dirs: + paths[os.path.join(base, d)] = src + d + '/', os.path.join(dst, d) + for f in files: + target = os.path.join(dst, f) + target = progress_filter(src + f, target) + if not target: + # skip non-files + continue + ensure_directory(target) + f = os.path.join(base, f) + shutil.copyfile(f, target) + shutil.copystat(f, target) + + +def unpack_zipfile(filename, extract_dir, progress_filter=default_filter): + """Unpack zip `filename` to `extract_dir` + + Raises ``UnrecognizedFormat`` if `filename` is not a zipfile (as determined + by ``zipfile.is_zipfile()``). See ``unpack_archive()`` for an explanation + of the `progress_filter` argument. + """ + + if not zipfile.is_zipfile(filename): + raise UnrecognizedFormat("%s is not a zip file" % (filename,)) + + with zipfile.ZipFile(filename) as z: + for info in z.infolist(): + name = info.filename + + # don't extract absolute paths or ones with .. in them + if name.startswith('/') or '..' in name.split('/'): + continue + + target = os.path.join(extract_dir, *name.split('/')) + target = progress_filter(name, target) + if not target: + continue + if name.endswith('/'): + # directory + ensure_directory(target) + else: + # file + ensure_directory(target) + data = z.read(info.filename) + with open(target, 'wb') as f: + f.write(data) + unix_attributes = info.external_attr >> 16 + if unix_attributes: + os.chmod(target, unix_attributes) + + +def unpack_tarfile(filename, extract_dir, progress_filter=default_filter): + """Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir` + + Raises ``UnrecognizedFormat`` if `filename` is not a tarfile (as determined + by ``tarfile.open()``). See ``unpack_archive()`` for an explanation + of the `progress_filter` argument. + """ + try: + tarobj = tarfile.open(filename) + except tarfile.TarError: + raise UnrecognizedFormat( + "%s is not a compressed or uncompressed tar file" % (filename,) + ) + with contextlib.closing(tarobj): + # don't do any chowning! + tarobj.chown = lambda *args: None + for member in tarobj: + name = member.name + # don't extract absolute paths or ones with .. in them + if not name.startswith('/') and '..' not in name.split('/'): + prelim_dst = os.path.join(extract_dir, *name.split('/')) + + # resolve any links and to extract the link targets as normal + # files + while member is not None and (member.islnk() or member.issym()): + linkpath = member.linkname + if member.issym(): + base = posixpath.dirname(member.name) + linkpath = posixpath.join(base, linkpath) + linkpath = posixpath.normpath(linkpath) + member = tarobj._getmember(linkpath) + + if member is not None and (member.isfile() or member.isdir()): + final_dst = progress_filter(name, prelim_dst) + if final_dst: + if final_dst.endswith(os.sep): + final_dst = final_dst[:-1] + try: + # XXX Ugh + tarobj._extract_member(member, final_dst) + except tarfile.ExtractError: + # chown/chmod/mkfifo/mknode/makedev failed + pass + return True + + +extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile diff --git a/robot/lib/python3.8/site-packages/setuptools/build_meta.py b/robot/lib/python3.8/site-packages/setuptools/build_meta.py new file mode 100644 index 0000000000000000000000000000000000000000..10c4b528d996d23a1319e3fed755aef0e6da2eb9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/build_meta.py @@ -0,0 +1,257 @@ +"""A PEP 517 interface to setuptools + +Previously, when a user or a command line tool (let's call it a "frontend") +needed to make a request of setuptools to take a certain action, for +example, generating a list of installation requirements, the frontend would +would call "setup.py egg_info" or "setup.py bdist_wheel" on the command line. + +PEP 517 defines a different method of interfacing with setuptools. Rather +than calling "setup.py" directly, the frontend should: + + 1. Set the current directory to the directory with a setup.py file + 2. Import this module into a safe python interpreter (one in which + setuptools can potentially set global variables or crash hard). + 3. Call one of the functions defined in PEP 517. + +What each function does is defined in PEP 517. However, here is a "casual" +definition of the functions (this definition should not be relied on for +bug reports or API stability): + + - `build_wheel`: build a wheel in the folder and return the basename + - `get_requires_for_build_wheel`: get the `setup_requires` to build + - `prepare_metadata_for_build_wheel`: get the `install_requires` + - `build_sdist`: build an sdist in the folder and return the basename + - `get_requires_for_build_sdist`: get the `setup_requires` to build + +Again, this is not a formal definition! Just a "taste" of the module. +""" + +import io +import os +import sys +import tokenize +import shutil +import contextlib + +import setuptools +import distutils +from setuptools.py31compat import TemporaryDirectory + +from pkg_resources import parse_requirements +from pkg_resources.py31compat import makedirs + +__all__ = ['get_requires_for_build_sdist', + 'get_requires_for_build_wheel', + 'prepare_metadata_for_build_wheel', + 'build_wheel', + 'build_sdist', + '__legacy__', + 'SetupRequirementsError'] + +class SetupRequirementsError(BaseException): + def __init__(self, specifiers): + self.specifiers = specifiers + + +class Distribution(setuptools.dist.Distribution): + def fetch_build_eggs(self, specifiers): + specifier_list = list(map(str, parse_requirements(specifiers))) + + raise SetupRequirementsError(specifier_list) + + @classmethod + @contextlib.contextmanager + def patch(cls): + """ + Replace + distutils.dist.Distribution with this class + for the duration of this context. + """ + orig = distutils.core.Distribution + distutils.core.Distribution = cls + try: + yield + finally: + distutils.core.Distribution = orig + + +def _to_str(s): + """ + Convert a filename to a string (on Python 2, explicitly + a byte string, not Unicode) as distutils checks for the + exact type str. + """ + if sys.version_info[0] == 2 and not isinstance(s, str): + # Assume it's Unicode, as that's what the PEP says + # should be provided. + return s.encode(sys.getfilesystemencoding()) + return s + + +def _get_immediate_subdirectories(a_dir): + return [name for name in os.listdir(a_dir) + if os.path.isdir(os.path.join(a_dir, name))] + + +def _file_with_extension(directory, extension): + matching = ( + f for f in os.listdir(directory) + if f.endswith(extension) + ) + file, = matching + return file + + +def _open_setup_script(setup_script): + if not os.path.exists(setup_script): + # Supply a default setup.py + return io.StringIO(u"from setuptools import setup; setup()") + + return getattr(tokenize, 'open', open)(setup_script) + + +class _BuildMetaBackend(object): + + def _fix_config(self, config_settings): + config_settings = config_settings or {} + config_settings.setdefault('--global-option', []) + return config_settings + + def _get_build_requires(self, config_settings, requirements): + config_settings = self._fix_config(config_settings) + + sys.argv = sys.argv[:1] + ['egg_info'] + \ + config_settings["--global-option"] + try: + with Distribution.patch(): + self.run_setup() + except SetupRequirementsError as e: + requirements += e.specifiers + + return requirements + + def run_setup(self, setup_script='setup.py'): + # Note that we can reuse our build directory between calls + # Correctness comes first, then optimization later + __file__ = setup_script + __name__ = '__main__' + + with _open_setup_script(__file__) as f: + code = f.read().replace(r'\r\n', r'\n') + + exec(compile(code, __file__, 'exec'), locals()) + + def get_requires_for_build_wheel(self, config_settings=None): + config_settings = self._fix_config(config_settings) + return self._get_build_requires(config_settings, requirements=['wheel']) + + def get_requires_for_build_sdist(self, config_settings=None): + config_settings = self._fix_config(config_settings) + return self._get_build_requires(config_settings, requirements=[]) + + def prepare_metadata_for_build_wheel(self, metadata_directory, + config_settings=None): + sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', + _to_str(metadata_directory)] + self.run_setup() + + dist_info_directory = metadata_directory + while True: + dist_infos = [f for f in os.listdir(dist_info_directory) + if f.endswith('.dist-info')] + + if (len(dist_infos) == 0 and + len(_get_immediate_subdirectories(dist_info_directory)) == 1): + + dist_info_directory = os.path.join( + dist_info_directory, os.listdir(dist_info_directory)[0]) + continue + + assert len(dist_infos) == 1 + break + + # PEP 517 requires that the .dist-info directory be placed in the + # metadata_directory. To comply, we MUST copy the directory to the root + if dist_info_directory != metadata_directory: + shutil.move( + os.path.join(dist_info_directory, dist_infos[0]), + metadata_directory) + shutil.rmtree(dist_info_directory, ignore_errors=True) + + return dist_infos[0] + + def _build_with_temp_dir(self, setup_command, result_extension, + result_directory, config_settings): + config_settings = self._fix_config(config_settings) + result_directory = os.path.abspath(result_directory) + + # Build in a temporary directory, then copy to the target. + makedirs(result_directory, exist_ok=True) + with TemporaryDirectory(dir=result_directory) as tmp_dist_dir: + sys.argv = (sys.argv[:1] + setup_command + + ['--dist-dir', tmp_dist_dir] + + config_settings["--global-option"]) + self.run_setup() + + result_basename = _file_with_extension(tmp_dist_dir, result_extension) + result_path = os.path.join(result_directory, result_basename) + if os.path.exists(result_path): + # os.rename will fail overwriting on non-Unix. + os.remove(result_path) + os.rename(os.path.join(tmp_dist_dir, result_basename), result_path) + + return result_basename + + + def build_wheel(self, wheel_directory, config_settings=None, + metadata_directory=None): + return self._build_with_temp_dir(['bdist_wheel'], '.whl', + wheel_directory, config_settings) + + def build_sdist(self, sdist_directory, config_settings=None): + return self._build_with_temp_dir(['sdist', '--formats', 'gztar'], + '.tar.gz', sdist_directory, + config_settings) + + +class _BuildMetaLegacyBackend(_BuildMetaBackend): + """Compatibility backend for setuptools + + This is a version of setuptools.build_meta that endeavors to maintain backwards + compatibility with pre-PEP 517 modes of invocation. It exists as a temporary + bridge between the old packaging mechanism and the new packaging mechanism, + and will eventually be removed. + """ + def run_setup(self, setup_script='setup.py'): + # In order to maintain compatibility with scripts assuming that + # the setup.py script is in a directory on the PYTHONPATH, inject + # '' into sys.path. (pypa/setuptools#1642) + sys_path = list(sys.path) # Save the original path + + script_dir = os.path.dirname(os.path.abspath(setup_script)) + if script_dir not in sys.path: + sys.path.insert(0, script_dir) + + try: + super(_BuildMetaLegacyBackend, + self).run_setup(setup_script=setup_script) + finally: + # While PEP 517 frontends should be calling each hook in a fresh + # subprocess according to the standard (and thus it should not be + # strictly necessary to restore the old sys.path), we'll restore + # the original path so that the path manipulation does not persist + # within the hook after run_setup is called. + sys.path[:] = sys_path + +# The primary backend +_BACKEND = _BuildMetaBackend() + +get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel +get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist +prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel +build_wheel = _BACKEND.build_wheel +build_sdist = _BACKEND.build_sdist + + +# The legacy backend +__legacy__ = _BuildMetaLegacyBackend() diff --git a/robot/lib/python3.8/site-packages/setuptools/cli-32.exe b/robot/lib/python3.8/site-packages/setuptools/cli-32.exe new file mode 100644 index 0000000000000000000000000000000000000000..b1487b7819e7286577a043c7726fbe0ca1543083 Binary files /dev/null and b/robot/lib/python3.8/site-packages/setuptools/cli-32.exe differ diff --git a/robot/lib/python3.8/site-packages/setuptools/cli-64.exe b/robot/lib/python3.8/site-packages/setuptools/cli-64.exe new file mode 100644 index 0000000000000000000000000000000000000000..675e6bf3743f3d3011c238657e7128ee9960ef7f Binary files /dev/null and b/robot/lib/python3.8/site-packages/setuptools/cli-64.exe differ diff --git a/robot/lib/python3.8/site-packages/setuptools/cli.exe b/robot/lib/python3.8/site-packages/setuptools/cli.exe new file mode 100644 index 0000000000000000000000000000000000000000..b1487b7819e7286577a043c7726fbe0ca1543083 Binary files /dev/null and b/robot/lib/python3.8/site-packages/setuptools/cli.exe differ diff --git a/robot/lib/python3.8/site-packages/setuptools/command/__init__.py b/robot/lib/python3.8/site-packages/setuptools/command/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..743f5588faf3ad79850df7bd196749e7a6c03f93 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/__init__.py @@ -0,0 +1,17 @@ +__all__ = [ + 'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop', + 'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts', + 'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts', + 'bdist_wininst', 'upload_docs', 'build_clib', 'dist_info', +] + +from distutils.command.bdist import bdist +import sys + +from setuptools.command import install_scripts + +if 'egg' not in bdist.format_commands: + bdist.format_command['egg'] = ('bdist_egg', "Python .egg file") + bdist.format_commands.append('egg') + +del bdist, sys diff --git a/robot/lib/python3.8/site-packages/setuptools/command/alias.py b/robot/lib/python3.8/site-packages/setuptools/command/alias.py new file mode 100644 index 0000000000000000000000000000000000000000..4532b1cc0dca76227927e873f9c64f01008e565a --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/alias.py @@ -0,0 +1,80 @@ +from distutils.errors import DistutilsOptionError + +from setuptools.extern.six.moves import map + +from setuptools.command.setopt import edit_config, option_base, config_file + + +def shquote(arg): + """Quote an argument for later parsing by shlex.split()""" + for c in '"', "'", "\\", "#": + if c in arg: + return repr(arg) + if arg.split() != [arg]: + return repr(arg) + return arg + + +class alias(option_base): + """Define a shortcut that invokes one or more commands""" + + description = "define a shortcut to invoke one or more commands" + command_consumes_arguments = True + + user_options = [ + ('remove', 'r', 'remove (unset) the alias'), + ] + option_base.user_options + + boolean_options = option_base.boolean_options + ['remove'] + + def initialize_options(self): + option_base.initialize_options(self) + self.args = None + self.remove = None + + def finalize_options(self): + option_base.finalize_options(self) + if self.remove and len(self.args) != 1: + raise DistutilsOptionError( + "Must specify exactly one argument (the alias name) when " + "using --remove" + ) + + def run(self): + aliases = self.distribution.get_option_dict('aliases') + + if not self.args: + print("Command Aliases") + print("---------------") + for alias in aliases: + print("setup.py alias", format_alias(alias, aliases)) + return + + elif len(self.args) == 1: + alias, = self.args + if self.remove: + command = None + elif alias in aliases: + print("setup.py alias", format_alias(alias, aliases)) + return + else: + print("No alias definition found for %r" % alias) + return + else: + alias = self.args[0] + command = ' '.join(map(shquote, self.args[1:])) + + edit_config(self.filename, {'aliases': {alias: command}}, self.dry_run) + + +def format_alias(name, aliases): + source, command = aliases[name] + if source == config_file('global'): + source = '--global-config ' + elif source == config_file('user'): + source = '--user-config ' + elif source == config_file('local'): + source = '' + else: + source = '--filename=%r' % source + return source + name + ' ' + command diff --git a/robot/lib/python3.8/site-packages/setuptools/command/bdist_egg.py b/robot/lib/python3.8/site-packages/setuptools/command/bdist_egg.py new file mode 100644 index 0000000000000000000000000000000000000000..98470f1715b21befab94b3e84428622a1ba86463 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/bdist_egg.py @@ -0,0 +1,502 @@ +"""setuptools.command.bdist_egg + +Build .egg distributions""" + +from distutils.errors import DistutilsSetupError +from distutils.dir_util import remove_tree, mkpath +from distutils import log +from types import CodeType +import sys +import os +import re +import textwrap +import marshal + +from setuptools.extern import six + +from pkg_resources import get_build_platform, Distribution, ensure_directory +from pkg_resources import EntryPoint +from setuptools.extension import Library +from setuptools import Command + +try: + # Python 2.7 or >=3.2 + from sysconfig import get_path, get_python_version + + def _get_purelib(): + return get_path("purelib") +except ImportError: + from distutils.sysconfig import get_python_lib, get_python_version + + def _get_purelib(): + return get_python_lib(False) + + +def strip_module(filename): + if '.' in filename: + filename = os.path.splitext(filename)[0] + if filename.endswith('module'): + filename = filename[:-6] + return filename + + +def sorted_walk(dir): + """Do os.walk in a reproducible way, + independent of indeterministic filesystem readdir order + """ + for base, dirs, files in os.walk(dir): + dirs.sort() + files.sort() + yield base, dirs, files + + +def write_stub(resource, pyfile): + _stub_template = textwrap.dedent(""" + def __bootstrap__(): + global __bootstrap__, __loader__, __file__ + import sys, pkg_resources, imp + __file__ = pkg_resources.resource_filename(__name__, %r) + __loader__ = None; del __bootstrap__, __loader__ + imp.load_dynamic(__name__,__file__) + __bootstrap__() + """).lstrip() + with open(pyfile, 'w') as f: + f.write(_stub_template % resource) + + +class bdist_egg(Command): + description = "create an \"egg\" distribution" + + user_options = [ + ('bdist-dir=', 'b', + "temporary directory for creating the distribution"), + ('plat-name=', 'p', "platform name to embed in generated filenames " + "(default: %s)" % get_build_platform()), + ('exclude-source-files', None, + "remove all .py files from the generated egg"), + ('keep-temp', 'k', + "keep the pseudo-installation tree around after " + + "creating the distribution archive"), + ('dist-dir=', 'd', + "directory to put final built distributions in"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ] + + boolean_options = [ + 'keep-temp', 'skip-build', 'exclude-source-files' + ] + + def initialize_options(self): + self.bdist_dir = None + self.plat_name = None + self.keep_temp = 0 + self.dist_dir = None + self.skip_build = 0 + self.egg_output = None + self.exclude_source_files = None + + def finalize_options(self): + ei_cmd = self.ei_cmd = self.get_finalized_command("egg_info") + self.egg_info = ei_cmd.egg_info + + if self.bdist_dir is None: + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'egg') + + if self.plat_name is None: + self.plat_name = get_build_platform() + + self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) + + if self.egg_output is None: + + # Compute filename of the output egg + basename = Distribution( + None, None, ei_cmd.egg_name, ei_cmd.egg_version, + get_python_version(), + self.distribution.has_ext_modules() and self.plat_name + ).egg_name() + + self.egg_output = os.path.join(self.dist_dir, basename + '.egg') + + def do_install_data(self): + # Hack for packages that install data to install's --install-lib + self.get_finalized_command('install').install_lib = self.bdist_dir + + site_packages = os.path.normcase(os.path.realpath(_get_purelib())) + old, self.distribution.data_files = self.distribution.data_files, [] + + for item in old: + if isinstance(item, tuple) and len(item) == 2: + if os.path.isabs(item[0]): + realpath = os.path.realpath(item[0]) + normalized = os.path.normcase(realpath) + if normalized == site_packages or normalized.startswith( + site_packages + os.sep + ): + item = realpath[len(site_packages) + 1:], item[1] + # XXX else: raise ??? + self.distribution.data_files.append(item) + + try: + log.info("installing package data to %s", self.bdist_dir) + self.call_command('install_data', force=0, root=None) + finally: + self.distribution.data_files = old + + def get_outputs(self): + return [self.egg_output] + + def call_command(self, cmdname, **kw): + """Invoke reinitialized command `cmdname` with keyword args""" + for dirname in INSTALL_DIRECTORY_ATTRS: + kw.setdefault(dirname, self.bdist_dir) + kw.setdefault('skip_build', self.skip_build) + kw.setdefault('dry_run', self.dry_run) + cmd = self.reinitialize_command(cmdname, **kw) + self.run_command(cmdname) + return cmd + + def run(self): + # Generate metadata first + self.run_command("egg_info") + # We run install_lib before install_data, because some data hacks + # pull their data path from the install_lib command. + log.info("installing library code to %s", self.bdist_dir) + instcmd = self.get_finalized_command('install') + old_root = instcmd.root + instcmd.root = None + if self.distribution.has_c_libraries() and not self.skip_build: + self.run_command('build_clib') + cmd = self.call_command('install_lib', warn_dir=0) + instcmd.root = old_root + + all_outputs, ext_outputs = self.get_ext_outputs() + self.stubs = [] + to_compile = [] + for (p, ext_name) in enumerate(ext_outputs): + filename, ext = os.path.splitext(ext_name) + pyfile = os.path.join(self.bdist_dir, strip_module(filename) + + '.py') + self.stubs.append(pyfile) + log.info("creating stub loader for %s", ext_name) + if not self.dry_run: + write_stub(os.path.basename(ext_name), pyfile) + to_compile.append(pyfile) + ext_outputs[p] = ext_name.replace(os.sep, '/') + + if to_compile: + cmd.byte_compile(to_compile) + if self.distribution.data_files: + self.do_install_data() + + # Make the EGG-INFO directory + archive_root = self.bdist_dir + egg_info = os.path.join(archive_root, 'EGG-INFO') + self.mkpath(egg_info) + if self.distribution.scripts: + script_dir = os.path.join(egg_info, 'scripts') + log.info("installing scripts to %s", script_dir) + self.call_command('install_scripts', install_dir=script_dir, + no_ep=1) + + self.copy_metadata_to(egg_info) + native_libs = os.path.join(egg_info, "native_libs.txt") + if all_outputs: + log.info("writing %s", native_libs) + if not self.dry_run: + ensure_directory(native_libs) + libs_file = open(native_libs, 'wt') + libs_file.write('\n'.join(all_outputs)) + libs_file.write('\n') + libs_file.close() + elif os.path.isfile(native_libs): + log.info("removing %s", native_libs) + if not self.dry_run: + os.unlink(native_libs) + + write_safety_flag( + os.path.join(archive_root, 'EGG-INFO'), self.zip_safe() + ) + + if os.path.exists(os.path.join(self.egg_info, 'depends.txt')): + log.warn( + "WARNING: 'depends.txt' will not be used by setuptools 0.6!\n" + "Use the install_requires/extras_require setup() args instead." + ) + + if self.exclude_source_files: + self.zap_pyfiles() + + # Make the archive + make_zipfile(self.egg_output, archive_root, verbose=self.verbose, + dry_run=self.dry_run, mode=self.gen_header()) + if not self.keep_temp: + remove_tree(self.bdist_dir, dry_run=self.dry_run) + + # Add to 'Distribution.dist_files' so that the "upload" command works + getattr(self.distribution, 'dist_files', []).append( + ('bdist_egg', get_python_version(), self.egg_output)) + + def zap_pyfiles(self): + log.info("Removing .py files from temporary directory") + for base, dirs, files in walk_egg(self.bdist_dir): + for name in files: + path = os.path.join(base, name) + + if name.endswith('.py'): + log.debug("Deleting %s", path) + os.unlink(path) + + if base.endswith('__pycache__'): + path_old = path + + pattern = r'(?P.+)\.(?P[^.]+)\.pyc' + m = re.match(pattern, name) + path_new = os.path.join( + base, os.pardir, m.group('name') + '.pyc') + log.info( + "Renaming file from [%s] to [%s]" + % (path_old, path_new)) + try: + os.remove(path_new) + except OSError: + pass + os.rename(path_old, path_new) + + def zip_safe(self): + safe = getattr(self.distribution, 'zip_safe', None) + if safe is not None: + return safe + log.warn("zip_safe flag not set; analyzing archive contents...") + return analyze_egg(self.bdist_dir, self.stubs) + + def gen_header(self): + epm = EntryPoint.parse_map(self.distribution.entry_points or '') + ep = epm.get('setuptools.installation', {}).get('eggsecutable') + if ep is None: + return 'w' # not an eggsecutable, do it the usual way. + + if not ep.attrs or ep.extras: + raise DistutilsSetupError( + "eggsecutable entry point (%r) cannot have 'extras' " + "or refer to a module" % (ep,) + ) + + pyver = '{}.{}'.format(*sys.version_info) + pkg = ep.module_name + full = '.'.join(ep.attrs) + base = ep.attrs[0] + basename = os.path.basename(self.egg_output) + + header = ( + "#!/bin/sh\n" + 'if [ `basename $0` = "%(basename)s" ]\n' + 'then exec python%(pyver)s -c "' + "import sys, os; sys.path.insert(0, os.path.abspath('$0')); " + "from %(pkg)s import %(base)s; sys.exit(%(full)s())" + '" "$@"\n' + 'else\n' + ' echo $0 is not the correct name for this egg file.\n' + ' echo Please rename it back to %(basename)s and try again.\n' + ' exec false\n' + 'fi\n' + ) % locals() + + if not self.dry_run: + mkpath(os.path.dirname(self.egg_output), dry_run=self.dry_run) + f = open(self.egg_output, 'w') + f.write(header) + f.close() + return 'a' + + def copy_metadata_to(self, target_dir): + "Copy metadata (egg info) to the target_dir" + # normalize the path (so that a forward-slash in egg_info will + # match using startswith below) + norm_egg_info = os.path.normpath(self.egg_info) + prefix = os.path.join(norm_egg_info, '') + for path in self.ei_cmd.filelist.files: + if path.startswith(prefix): + target = os.path.join(target_dir, path[len(prefix):]) + ensure_directory(target) + self.copy_file(path, target) + + def get_ext_outputs(self): + """Get a list of relative paths to C extensions in the output distro""" + + all_outputs = [] + ext_outputs = [] + + paths = {self.bdist_dir: ''} + for base, dirs, files in sorted_walk(self.bdist_dir): + for filename in files: + if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS: + all_outputs.append(paths[base] + filename) + for filename in dirs: + paths[os.path.join(base, filename)] = (paths[base] + + filename + '/') + + if self.distribution.has_ext_modules(): + build_cmd = self.get_finalized_command('build_ext') + for ext in build_cmd.extensions: + if isinstance(ext, Library): + continue + fullname = build_cmd.get_ext_fullname(ext.name) + filename = build_cmd.get_ext_filename(fullname) + if not os.path.basename(filename).startswith('dl-'): + if os.path.exists(os.path.join(self.bdist_dir, filename)): + ext_outputs.append(filename) + + return all_outputs, ext_outputs + + +NATIVE_EXTENSIONS = dict.fromkeys('.dll .so .dylib .pyd'.split()) + + +def walk_egg(egg_dir): + """Walk an unpacked egg's contents, skipping the metadata directory""" + walker = sorted_walk(egg_dir) + base, dirs, files = next(walker) + if 'EGG-INFO' in dirs: + dirs.remove('EGG-INFO') + yield base, dirs, files + for bdf in walker: + yield bdf + + +def analyze_egg(egg_dir, stubs): + # check for existing flag in EGG-INFO + for flag, fn in safety_flags.items(): + if os.path.exists(os.path.join(egg_dir, 'EGG-INFO', fn)): + return flag + if not can_scan(): + return False + safe = True + for base, dirs, files in walk_egg(egg_dir): + for name in files: + if name.endswith('.py') or name.endswith('.pyw'): + continue + elif name.endswith('.pyc') or name.endswith('.pyo'): + # always scan, even if we already know we're not safe + safe = scan_module(egg_dir, base, name, stubs) and safe + return safe + + +def write_safety_flag(egg_dir, safe): + # Write or remove zip safety flag file(s) + for flag, fn in safety_flags.items(): + fn = os.path.join(egg_dir, fn) + if os.path.exists(fn): + if safe is None or bool(safe) != flag: + os.unlink(fn) + elif safe is not None and bool(safe) == flag: + f = open(fn, 'wt') + f.write('\n') + f.close() + + +safety_flags = { + True: 'zip-safe', + False: 'not-zip-safe', +} + + +def scan_module(egg_dir, base, name, stubs): + """Check whether module possibly uses unsafe-for-zipfile stuff""" + + filename = os.path.join(base, name) + if filename[:-1] in stubs: + return True # Extension module + pkg = base[len(egg_dir) + 1:].replace(os.sep, '.') + module = pkg + (pkg and '.' or '') + os.path.splitext(name)[0] + if six.PY2: + skip = 8 # skip magic & date + elif sys.version_info < (3, 7): + skip = 12 # skip magic & date & file size + else: + skip = 16 # skip magic & reserved? & date & file size + f = open(filename, 'rb') + f.read(skip) + code = marshal.load(f) + f.close() + safe = True + symbols = dict.fromkeys(iter_symbols(code)) + for bad in ['__file__', '__path__']: + if bad in symbols: + log.warn("%s: module references %s", module, bad) + safe = False + if 'inspect' in symbols: + for bad in [ + 'getsource', 'getabsfile', 'getsourcefile', 'getfile' + 'getsourcelines', 'findsource', 'getcomments', 'getframeinfo', + 'getinnerframes', 'getouterframes', 'stack', 'trace' + ]: + if bad in symbols: + log.warn("%s: module MAY be using inspect.%s", module, bad) + safe = False + return safe + + +def iter_symbols(code): + """Yield names and strings used by `code` and its nested code objects""" + for name in code.co_names: + yield name + for const in code.co_consts: + if isinstance(const, six.string_types): + yield const + elif isinstance(const, CodeType): + for name in iter_symbols(const): + yield name + + +def can_scan(): + if not sys.platform.startswith('java') and sys.platform != 'cli': + # CPython, PyPy, etc. + return True + log.warn("Unable to analyze compiled code on this platform.") + log.warn("Please ask the author to include a 'zip_safe'" + " setting (either True or False) in the package's setup.py") + + +# Attribute names of options for commands that might need to be convinced to +# install to the egg build directory + +INSTALL_DIRECTORY_ATTRS = [ + 'install_lib', 'install_dir', 'install_data', 'install_base' +] + + +def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=True, + mode='w'): + """Create a zip file from all the files under 'base_dir'. The output + zip file will be named 'base_dir' + ".zip". Uses either the "zipfile" + Python module (if available) or the InfoZIP "zip" utility (if installed + and found on the default search path). If neither tool is available, + raises DistutilsExecError. Returns the name of the output zip file. + """ + import zipfile + + mkpath(os.path.dirname(zip_filename), dry_run=dry_run) + log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir) + + def visit(z, dirname, names): + for name in names: + path = os.path.normpath(os.path.join(dirname, name)) + if os.path.isfile(path): + p = path[len(base_dir) + 1:] + if not dry_run: + z.write(path, p) + log.debug("adding '%s'", p) + + compression = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED + if not dry_run: + z = zipfile.ZipFile(zip_filename, mode, compression=compression) + for dirname, dirs, files in sorted_walk(base_dir): + visit(z, dirname, files) + z.close() + else: + for dirname, dirs, files in sorted_walk(base_dir): + visit(None, dirname, files) + return zip_filename diff --git a/robot/lib/python3.8/site-packages/setuptools/command/bdist_rpm.py b/robot/lib/python3.8/site-packages/setuptools/command/bdist_rpm.py new file mode 100644 index 0000000000000000000000000000000000000000..70730927ecaed778ebbdee98eb37c24ec3f1a8e6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/bdist_rpm.py @@ -0,0 +1,43 @@ +import distutils.command.bdist_rpm as orig + + +class bdist_rpm(orig.bdist_rpm): + """ + Override the default bdist_rpm behavior to do the following: + + 1. Run egg_info to ensure the name and version are properly calculated. + 2. Always run 'install' using --single-version-externally-managed to + disable eggs in RPM distributions. + 3. Replace dash with underscore in the version numbers for better RPM + compatibility. + """ + + def run(self): + # ensure distro name is up-to-date + self.run_command('egg_info') + + orig.bdist_rpm.run(self) + + def _make_spec_file(self): + version = self.distribution.get_version() + rpmversion = version.replace('-', '_') + spec = orig.bdist_rpm._make_spec_file(self) + line23 = '%define version ' + version + line24 = '%define version ' + rpmversion + spec = [ + line.replace( + "Source0: %{name}-%{version}.tar", + "Source0: %{name}-%{unmangled_version}.tar" + ).replace( + "setup.py install ", + "setup.py install --single-version-externally-managed " + ).replace( + "%setup", + "%setup -n %{name}-%{unmangled_version}" + ).replace(line23, line24) + for line in spec + ] + insert_loc = spec.index(line24) + 1 + unmangled_version = "%define unmangled_version " + version + spec.insert(insert_loc, unmangled_version) + return spec diff --git a/robot/lib/python3.8/site-packages/setuptools/command/bdist_wininst.py b/robot/lib/python3.8/site-packages/setuptools/command/bdist_wininst.py new file mode 100644 index 0000000000000000000000000000000000000000..073de97b46c92e2e221cade8c1350ab2c5cff891 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/bdist_wininst.py @@ -0,0 +1,21 @@ +import distutils.command.bdist_wininst as orig + + +class bdist_wininst(orig.bdist_wininst): + def reinitialize_command(self, command, reinit_subcommands=0): + """ + Supplement reinitialize_command to work around + http://bugs.python.org/issue20819 + """ + cmd = self.distribution.reinitialize_command( + command, reinit_subcommands) + if command in ('install', 'install_lib'): + cmd.install_lib = None + return cmd + + def run(self): + self._is_running = True + try: + orig.bdist_wininst.run(self) + finally: + self._is_running = False diff --git a/robot/lib/python3.8/site-packages/setuptools/command/build_clib.py b/robot/lib/python3.8/site-packages/setuptools/command/build_clib.py new file mode 100644 index 0000000000000000000000000000000000000000..09caff6ffde8fc3f368cb635dc3cbbbc8851530d --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/build_clib.py @@ -0,0 +1,98 @@ +import distutils.command.build_clib as orig +from distutils.errors import DistutilsSetupError +from distutils import log +from setuptools.dep_util import newer_pairwise_group + + +class build_clib(orig.build_clib): + """ + Override the default build_clib behaviour to do the following: + + 1. Implement a rudimentary timestamp-based dependency system + so 'compile()' doesn't run every time. + 2. Add more keys to the 'build_info' dictionary: + * obj_deps - specify dependencies for each object compiled. + this should be a dictionary mapping a key + with the source filename to a list of + dependencies. Use an empty string for global + dependencies. + * cflags - specify a list of additional flags to pass to + the compiler. + """ + + def build_libraries(self, libraries): + for (lib_name, build_info) in libraries: + sources = build_info.get('sources') + if sources is None or not isinstance(sources, (list, tuple)): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'sources' must be present and must be " + "a list of source filenames" % lib_name) + sources = list(sources) + + log.info("building '%s' library", lib_name) + + # Make sure everything is the correct type. + # obj_deps should be a dictionary of keys as sources + # and a list/tuple of files that are its dependencies. + obj_deps = build_info.get('obj_deps', dict()) + if not isinstance(obj_deps, dict): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'obj_deps' must be a dictionary of " + "type 'source: list'" % lib_name) + dependencies = [] + + # Get the global dependencies that are specified by the '' key. + # These will go into every source's dependency list. + global_deps = obj_deps.get('', list()) + if not isinstance(global_deps, (list, tuple)): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'obj_deps' must be a dictionary of " + "type 'source: list'" % lib_name) + + # Build the list to be used by newer_pairwise_group + # each source will be auto-added to its dependencies. + for source in sources: + src_deps = [source] + src_deps.extend(global_deps) + extra_deps = obj_deps.get(source, list()) + if not isinstance(extra_deps, (list, tuple)): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'obj_deps' must be a dictionary of " + "type 'source: list'" % lib_name) + src_deps.extend(extra_deps) + dependencies.append(src_deps) + + expected_objects = self.compiler.object_filenames( + sources, + output_dir=self.build_temp + ) + + if newer_pairwise_group(dependencies, expected_objects) != ([], []): + # First, compile the source code to object files in the library + # directory. (This should probably change to putting object + # files in a temporary build directory.) + macros = build_info.get('macros') + include_dirs = build_info.get('include_dirs') + cflags = build_info.get('cflags') + objects = self.compiler.compile( + sources, + output_dir=self.build_temp, + macros=macros, + include_dirs=include_dirs, + extra_postargs=cflags, + debug=self.debug + ) + + # Now "link" the object files together into a static library. + # (On Unix at least, this isn't really linking -- it just + # builds an archive. Whatever.) + self.compiler.create_static_lib( + expected_objects, + lib_name, + output_dir=self.build_clib, + debug=self.debug + ) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/build_ext.py b/robot/lib/python3.8/site-packages/setuptools/command/build_ext.py new file mode 100644 index 0000000000000000000000000000000000000000..daa8e4fe81c18e8fc3e07718b4a66137b062127e --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/build_ext.py @@ -0,0 +1,327 @@ +import os +import sys +import itertools +from distutils.command.build_ext import build_ext as _du_build_ext +from distutils.file_util import copy_file +from distutils.ccompiler import new_compiler +from distutils.sysconfig import customize_compiler, get_config_var +from distutils.errors import DistutilsError +from distutils import log + +from setuptools.extension import Library +from setuptools.extern import six + +if six.PY2: + import imp + + EXTENSION_SUFFIXES = [s for s, _, tp in imp.get_suffixes() if tp == imp.C_EXTENSION] +else: + from importlib.machinery import EXTENSION_SUFFIXES + +try: + # Attempt to use Cython for building extensions, if available + from Cython.Distutils.build_ext import build_ext as _build_ext + # Additionally, assert that the compiler module will load + # also. Ref #1229. + __import__('Cython.Compiler.Main') +except ImportError: + _build_ext = _du_build_ext + +# make sure _config_vars is initialized +get_config_var("LDSHARED") +from distutils.sysconfig import _config_vars as _CONFIG_VARS + + +def _customize_compiler_for_shlib(compiler): + if sys.platform == "darwin": + # building .dylib requires additional compiler flags on OSX; here we + # temporarily substitute the pyconfig.h variables so that distutils' + # 'customize_compiler' uses them before we build the shared libraries. + tmp = _CONFIG_VARS.copy() + try: + # XXX Help! I don't have any idea whether these are right... + _CONFIG_VARS['LDSHARED'] = ( + "gcc -Wl,-x -dynamiclib -undefined dynamic_lookup") + _CONFIG_VARS['CCSHARED'] = " -dynamiclib" + _CONFIG_VARS['SO'] = ".dylib" + customize_compiler(compiler) + finally: + _CONFIG_VARS.clear() + _CONFIG_VARS.update(tmp) + else: + customize_compiler(compiler) + + +have_rtld = False +use_stubs = False +libtype = 'shared' + +if sys.platform == "darwin": + use_stubs = True +elif os.name != 'nt': + try: + import dl + use_stubs = have_rtld = hasattr(dl, 'RTLD_NOW') + except ImportError: + pass + +if_dl = lambda s: s if have_rtld else '' + + +def get_abi3_suffix(): + """Return the file extension for an abi3-compliant Extension()""" + for suffix in EXTENSION_SUFFIXES: + if '.abi3' in suffix: # Unix + return suffix + elif suffix == '.pyd': # Windows + return suffix + + +class build_ext(_build_ext): + def run(self): + """Build extensions in build directory, then copy if --inplace""" + old_inplace, self.inplace = self.inplace, 0 + _build_ext.run(self) + self.inplace = old_inplace + if old_inplace: + self.copy_extensions_to_source() + + def copy_extensions_to_source(self): + build_py = self.get_finalized_command('build_py') + for ext in self.extensions: + fullname = self.get_ext_fullname(ext.name) + filename = self.get_ext_filename(fullname) + modpath = fullname.split('.') + package = '.'.join(modpath[:-1]) + package_dir = build_py.get_package_dir(package) + dest_filename = os.path.join(package_dir, + os.path.basename(filename)) + src_filename = os.path.join(self.build_lib, filename) + + # Always copy, even if source is older than destination, to ensure + # that the right extensions for the current Python/platform are + # used. + copy_file( + src_filename, dest_filename, verbose=self.verbose, + dry_run=self.dry_run + ) + if ext._needs_stub: + self.write_stub(package_dir or os.curdir, ext, True) + + def get_ext_filename(self, fullname): + filename = _build_ext.get_ext_filename(self, fullname) + if fullname in self.ext_map: + ext = self.ext_map[fullname] + use_abi3 = ( + six.PY3 + and getattr(ext, 'py_limited_api') + and get_abi3_suffix() + ) + if use_abi3: + so_ext = get_config_var('EXT_SUFFIX') + filename = filename[:-len(so_ext)] + filename = filename + get_abi3_suffix() + if isinstance(ext, Library): + fn, ext = os.path.splitext(filename) + return self.shlib_compiler.library_filename(fn, libtype) + elif use_stubs and ext._links_to_dynamic: + d, fn = os.path.split(filename) + return os.path.join(d, 'dl-' + fn) + return filename + + def initialize_options(self): + _build_ext.initialize_options(self) + self.shlib_compiler = None + self.shlibs = [] + self.ext_map = {} + + def finalize_options(self): + _build_ext.finalize_options(self) + self.extensions = self.extensions or [] + self.check_extensions_list(self.extensions) + self.shlibs = [ext for ext in self.extensions + if isinstance(ext, Library)] + if self.shlibs: + self.setup_shlib_compiler() + for ext in self.extensions: + ext._full_name = self.get_ext_fullname(ext.name) + for ext in self.extensions: + fullname = ext._full_name + self.ext_map[fullname] = ext + + # distutils 3.1 will also ask for module names + # XXX what to do with conflicts? + self.ext_map[fullname.split('.')[-1]] = ext + + ltd = self.shlibs and self.links_to_dynamic(ext) or False + ns = ltd and use_stubs and not isinstance(ext, Library) + ext._links_to_dynamic = ltd + ext._needs_stub = ns + filename = ext._file_name = self.get_ext_filename(fullname) + libdir = os.path.dirname(os.path.join(self.build_lib, filename)) + if ltd and libdir not in ext.library_dirs: + ext.library_dirs.append(libdir) + if ltd and use_stubs and os.curdir not in ext.runtime_library_dirs: + ext.runtime_library_dirs.append(os.curdir) + + def setup_shlib_compiler(self): + compiler = self.shlib_compiler = new_compiler( + compiler=self.compiler, dry_run=self.dry_run, force=self.force + ) + _customize_compiler_for_shlib(compiler) + + if self.include_dirs is not None: + compiler.set_include_dirs(self.include_dirs) + if self.define is not None: + # 'define' option is a list of (name,value) tuples + for (name, value) in self.define: + compiler.define_macro(name, value) + if self.undef is not None: + for macro in self.undef: + compiler.undefine_macro(macro) + if self.libraries is not None: + compiler.set_libraries(self.libraries) + if self.library_dirs is not None: + compiler.set_library_dirs(self.library_dirs) + if self.rpath is not None: + compiler.set_runtime_library_dirs(self.rpath) + if self.link_objects is not None: + compiler.set_link_objects(self.link_objects) + + # hack so distutils' build_extension() builds a library instead + compiler.link_shared_object = link_shared_object.__get__(compiler) + + def get_export_symbols(self, ext): + if isinstance(ext, Library): + return ext.export_symbols + return _build_ext.get_export_symbols(self, ext) + + def build_extension(self, ext): + ext._convert_pyx_sources_to_lang() + _compiler = self.compiler + try: + if isinstance(ext, Library): + self.compiler = self.shlib_compiler + _build_ext.build_extension(self, ext) + if ext._needs_stub: + cmd = self.get_finalized_command('build_py').build_lib + self.write_stub(cmd, ext) + finally: + self.compiler = _compiler + + def links_to_dynamic(self, ext): + """Return true if 'ext' links to a dynamic lib in the same package""" + # XXX this should check to ensure the lib is actually being built + # XXX as dynamic, and not just using a locally-found version or a + # XXX static-compiled version + libnames = dict.fromkeys([lib._full_name for lib in self.shlibs]) + pkg = '.'.join(ext._full_name.split('.')[:-1] + ['']) + return any(pkg + libname in libnames for libname in ext.libraries) + + def get_outputs(self): + return _build_ext.get_outputs(self) + self.__get_stubs_outputs() + + def __get_stubs_outputs(self): + # assemble the base name for each extension that needs a stub + ns_ext_bases = ( + os.path.join(self.build_lib, *ext._full_name.split('.')) + for ext in self.extensions + if ext._needs_stub + ) + # pair each base with the extension + pairs = itertools.product(ns_ext_bases, self.__get_output_extensions()) + return list(base + fnext for base, fnext in pairs) + + def __get_output_extensions(self): + yield '.py' + yield '.pyc' + if self.get_finalized_command('build_py').optimize: + yield '.pyo' + + def write_stub(self, output_dir, ext, compile=False): + log.info("writing stub loader for %s to %s", ext._full_name, + output_dir) + stub_file = (os.path.join(output_dir, *ext._full_name.split('.')) + + '.py') + if compile and os.path.exists(stub_file): + raise DistutilsError(stub_file + " already exists! Please delete.") + if not self.dry_run: + f = open(stub_file, 'w') + f.write( + '\n'.join([ + "def __bootstrap__():", + " global __bootstrap__, __file__, __loader__", + " import sys, os, pkg_resources, imp" + if_dl(", dl"), + " __file__ = pkg_resources.resource_filename" + "(__name__,%r)" + % os.path.basename(ext._file_name), + " del __bootstrap__", + " if '__loader__' in globals():", + " del __loader__", + if_dl(" old_flags = sys.getdlopenflags()"), + " old_dir = os.getcwd()", + " try:", + " os.chdir(os.path.dirname(__file__))", + if_dl(" sys.setdlopenflags(dl.RTLD_NOW)"), + " imp.load_dynamic(__name__,__file__)", + " finally:", + if_dl(" sys.setdlopenflags(old_flags)"), + " os.chdir(old_dir)", + "__bootstrap__()", + "" # terminal \n + ]) + ) + f.close() + if compile: + from distutils.util import byte_compile + + byte_compile([stub_file], optimize=0, + force=True, dry_run=self.dry_run) + optimize = self.get_finalized_command('install_lib').optimize + if optimize > 0: + byte_compile([stub_file], optimize=optimize, + force=True, dry_run=self.dry_run) + if os.path.exists(stub_file) and not self.dry_run: + os.unlink(stub_file) + + +if use_stubs or os.name == 'nt': + # Build shared libraries + # + def link_shared_object( + self, objects, output_libname, output_dir=None, libraries=None, + library_dirs=None, runtime_library_dirs=None, export_symbols=None, + debug=0, extra_preargs=None, extra_postargs=None, build_temp=None, + target_lang=None): + self.link( + self.SHARED_LIBRARY, objects, output_libname, + output_dir, libraries, library_dirs, runtime_library_dirs, + export_symbols, debug, extra_preargs, extra_postargs, + build_temp, target_lang + ) +else: + # Build static libraries everywhere else + libtype = 'static' + + def link_shared_object( + self, objects, output_libname, output_dir=None, libraries=None, + library_dirs=None, runtime_library_dirs=None, export_symbols=None, + debug=0, extra_preargs=None, extra_postargs=None, build_temp=None, + target_lang=None): + # XXX we need to either disallow these attrs on Library instances, + # or warn/abort here if set, or something... + # libraries=None, library_dirs=None, runtime_library_dirs=None, + # export_symbols=None, extra_preargs=None, extra_postargs=None, + # build_temp=None + + assert output_dir is None # distutils build_ext doesn't pass this + output_dir, filename = os.path.split(output_libname) + basename, ext = os.path.splitext(filename) + if self.library_filename("x").startswith('lib'): + # strip 'lib' prefix; this is kludgy if some platform uses + # a different prefix + basename = basename[3:] + + self.create_static_lib( + objects, basename, output_dir, debug, target_lang + ) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/build_py.py b/robot/lib/python3.8/site-packages/setuptools/command/build_py.py new file mode 100644 index 0000000000000000000000000000000000000000..b0314fd413ae7f8c1027ccde0b092fd493fb104b --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/build_py.py @@ -0,0 +1,270 @@ +from glob import glob +from distutils.util import convert_path +import distutils.command.build_py as orig +import os +import fnmatch +import textwrap +import io +import distutils.errors +import itertools + +from setuptools.extern import six +from setuptools.extern.six.moves import map, filter, filterfalse + +try: + from setuptools.lib2to3_ex import Mixin2to3 +except ImportError: + + class Mixin2to3: + def run_2to3(self, files, doctests=True): + "do nothing" + + +class build_py(orig.build_py, Mixin2to3): + """Enhanced 'build_py' command that includes data files with packages + + The data files are specified via a 'package_data' argument to 'setup()'. + See 'setuptools.dist.Distribution' for more details. + + Also, this version of the 'build_py' command allows you to specify both + 'py_modules' and 'packages' in the same setup operation. + """ + + def finalize_options(self): + orig.build_py.finalize_options(self) + self.package_data = self.distribution.package_data + self.exclude_package_data = (self.distribution.exclude_package_data or + {}) + if 'data_files' in self.__dict__: + del self.__dict__['data_files'] + self.__updated_files = [] + self.__doctests_2to3 = [] + + def run(self): + """Build modules, packages, and copy data files to build directory""" + if not self.py_modules and not self.packages: + return + + if self.py_modules: + self.build_modules() + + if self.packages: + self.build_packages() + self.build_package_data() + + self.run_2to3(self.__updated_files, False) + self.run_2to3(self.__updated_files, True) + self.run_2to3(self.__doctests_2to3, True) + + # Only compile actual .py files, using our base class' idea of what our + # output files are. + self.byte_compile(orig.build_py.get_outputs(self, include_bytecode=0)) + + def __getattr__(self, attr): + "lazily compute data files" + if attr == 'data_files': + self.data_files = self._get_data_files() + return self.data_files + return orig.build_py.__getattr__(self, attr) + + def build_module(self, module, module_file, package): + if six.PY2 and isinstance(package, six.string_types): + # avoid errors on Python 2 when unicode is passed (#190) + package = package.split('.') + outfile, copied = orig.build_py.build_module(self, module, module_file, + package) + if copied: + self.__updated_files.append(outfile) + return outfile, copied + + def _get_data_files(self): + """Generate list of '(package,src_dir,build_dir,filenames)' tuples""" + self.analyze_manifest() + return list(map(self._get_pkg_data_files, self.packages or ())) + + def _get_pkg_data_files(self, package): + # Locate package source directory + src_dir = self.get_package_dir(package) + + # Compute package build directory + build_dir = os.path.join(*([self.build_lib] + package.split('.'))) + + # Strip directory from globbed filenames + filenames = [ + os.path.relpath(file, src_dir) + for file in self.find_data_files(package, src_dir) + ] + return package, src_dir, build_dir, filenames + + def find_data_files(self, package, src_dir): + """Return filenames for package's data files in 'src_dir'""" + patterns = self._get_platform_patterns( + self.package_data, + package, + src_dir, + ) + globs_expanded = map(glob, patterns) + # flatten the expanded globs into an iterable of matches + globs_matches = itertools.chain.from_iterable(globs_expanded) + glob_files = filter(os.path.isfile, globs_matches) + files = itertools.chain( + self.manifest_files.get(package, []), + glob_files, + ) + return self.exclude_data_files(package, src_dir, files) + + def build_package_data(self): + """Copy data files into build directory""" + for package, src_dir, build_dir, filenames in self.data_files: + for filename in filenames: + target = os.path.join(build_dir, filename) + self.mkpath(os.path.dirname(target)) + srcfile = os.path.join(src_dir, filename) + outf, copied = self.copy_file(srcfile, target) + srcfile = os.path.abspath(srcfile) + if (copied and + srcfile in self.distribution.convert_2to3_doctests): + self.__doctests_2to3.append(outf) + + def analyze_manifest(self): + self.manifest_files = mf = {} + if not self.distribution.include_package_data: + return + src_dirs = {} + for package in self.packages or (): + # Locate package source directory + src_dirs[assert_relative(self.get_package_dir(package))] = package + + self.run_command('egg_info') + ei_cmd = self.get_finalized_command('egg_info') + for path in ei_cmd.filelist.files: + d, f = os.path.split(assert_relative(path)) + prev = None + oldf = f + while d and d != prev and d not in src_dirs: + prev = d + d, df = os.path.split(d) + f = os.path.join(df, f) + if d in src_dirs: + if path.endswith('.py') and f == oldf: + continue # it's a module, not data + mf.setdefault(src_dirs[d], []).append(path) + + def get_data_files(self): + pass # Lazily compute data files in _get_data_files() function. + + def check_package(self, package, package_dir): + """Check namespace packages' __init__ for declare_namespace""" + try: + return self.packages_checked[package] + except KeyError: + pass + + init_py = orig.build_py.check_package(self, package, package_dir) + self.packages_checked[package] = init_py + + if not init_py or not self.distribution.namespace_packages: + return init_py + + for pkg in self.distribution.namespace_packages: + if pkg == package or pkg.startswith(package + '.'): + break + else: + return init_py + + with io.open(init_py, 'rb') as f: + contents = f.read() + if b'declare_namespace' not in contents: + raise distutils.errors.DistutilsError( + "Namespace package problem: %s is a namespace package, but " + "its\n__init__.py does not call declare_namespace()! Please " + 'fix it.\n(See the setuptools manual under ' + '"Namespace Packages" for details.)\n"' % (package,) + ) + return init_py + + def initialize_options(self): + self.packages_checked = {} + orig.build_py.initialize_options(self) + + def get_package_dir(self, package): + res = orig.build_py.get_package_dir(self, package) + if self.distribution.src_root is not None: + return os.path.join(self.distribution.src_root, res) + return res + + def exclude_data_files(self, package, src_dir, files): + """Filter filenames for package's data files in 'src_dir'""" + files = list(files) + patterns = self._get_platform_patterns( + self.exclude_package_data, + package, + src_dir, + ) + match_groups = ( + fnmatch.filter(files, pattern) + for pattern in patterns + ) + # flatten the groups of matches into an iterable of matches + matches = itertools.chain.from_iterable(match_groups) + bad = set(matches) + keepers = ( + fn + for fn in files + if fn not in bad + ) + # ditch dupes + return list(_unique_everseen(keepers)) + + @staticmethod + def _get_platform_patterns(spec, package, src_dir): + """ + yield platform-specific path patterns (suitable for glob + or fn_match) from a glob-based spec (such as + self.package_data or self.exclude_package_data) + matching package in src_dir. + """ + raw_patterns = itertools.chain( + spec.get('', []), + spec.get(package, []), + ) + return ( + # Each pattern has to be converted to a platform-specific path + os.path.join(src_dir, convert_path(pattern)) + for pattern in raw_patterns + ) + + +# from Python docs +def _unique_everseen(iterable, key=None): + "List unique elements, preserving order. Remember all elements ever seen." + # unique_everseen('AAAABBBCCDAABBB') --> A B C D + # unique_everseen('ABBCcAD', str.lower) --> A B C D + seen = set() + seen_add = seen.add + if key is None: + for element in filterfalse(seen.__contains__, iterable): + seen_add(element) + yield element + else: + for element in iterable: + k = key(element) + if k not in seen: + seen_add(k) + yield element + + +def assert_relative(path): + if not os.path.isabs(path): + return path + from distutils.errors import DistutilsSetupError + + msg = textwrap.dedent(""" + Error: setup script specifies an absolute path: + + %s + + setup() arguments must *always* be /-separated paths relative to the + setup.py directory, *never* absolute paths. + """).lstrip() % path + raise DistutilsSetupError(msg) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/develop.py b/robot/lib/python3.8/site-packages/setuptools/command/develop.py new file mode 100644 index 0000000000000000000000000000000000000000..009e4f9368f5b29fffd160f3b712fb0cd19807bd --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/develop.py @@ -0,0 +1,221 @@ +from distutils.util import convert_path +from distutils import log +from distutils.errors import DistutilsError, DistutilsOptionError +import os +import glob +import io + +from setuptools.extern import six + +import pkg_resources +from setuptools.command.easy_install import easy_install +from setuptools import namespaces +import setuptools + +__metaclass__ = type + + +class develop(namespaces.DevelopInstaller, easy_install): + """Set up package for development""" + + description = "install package in 'development mode'" + + user_options = easy_install.user_options + [ + ("uninstall", "u", "Uninstall this source package"), + ("egg-path=", None, "Set the path to be used in the .egg-link file"), + ] + + boolean_options = easy_install.boolean_options + ['uninstall'] + + command_consumes_arguments = False # override base + + def run(self): + if self.uninstall: + self.multi_version = True + self.uninstall_link() + self.uninstall_namespaces() + else: + self.install_for_development() + self.warn_deprecated_options() + + def initialize_options(self): + self.uninstall = None + self.egg_path = None + easy_install.initialize_options(self) + self.setup_path = None + self.always_copy_from = '.' # always copy eggs installed in curdir + + def finalize_options(self): + ei = self.get_finalized_command("egg_info") + if ei.broken_egg_info: + template = "Please rename %r to %r before using 'develop'" + args = ei.egg_info, ei.broken_egg_info + raise DistutilsError(template % args) + self.args = [ei.egg_name] + + easy_install.finalize_options(self) + self.expand_basedirs() + self.expand_dirs() + # pick up setup-dir .egg files only: no .egg-info + self.package_index.scan(glob.glob('*.egg')) + + egg_link_fn = ei.egg_name + '.egg-link' + self.egg_link = os.path.join(self.install_dir, egg_link_fn) + self.egg_base = ei.egg_base + if self.egg_path is None: + self.egg_path = os.path.abspath(ei.egg_base) + + target = pkg_resources.normalize_path(self.egg_base) + egg_path = pkg_resources.normalize_path( + os.path.join(self.install_dir, self.egg_path)) + if egg_path != target: + raise DistutilsOptionError( + "--egg-path must be a relative path from the install" + " directory to " + target + ) + + # Make a distribution for the package's source + self.dist = pkg_resources.Distribution( + target, + pkg_resources.PathMetadata(target, os.path.abspath(ei.egg_info)), + project_name=ei.egg_name + ) + + self.setup_path = self._resolve_setup_path( + self.egg_base, + self.install_dir, + self.egg_path, + ) + + @staticmethod + def _resolve_setup_path(egg_base, install_dir, egg_path): + """ + Generate a path from egg_base back to '.' where the + setup script resides and ensure that path points to the + setup path from $install_dir/$egg_path. + """ + path_to_setup = egg_base.replace(os.sep, '/').rstrip('/') + if path_to_setup != os.curdir: + path_to_setup = '../' * (path_to_setup.count('/') + 1) + resolved = pkg_resources.normalize_path( + os.path.join(install_dir, egg_path, path_to_setup) + ) + if resolved != pkg_resources.normalize_path(os.curdir): + raise DistutilsOptionError( + "Can't get a consistent path to setup script from" + " installation directory", resolved, + pkg_resources.normalize_path(os.curdir)) + return path_to_setup + + def install_for_development(self): + if six.PY3 and getattr(self.distribution, 'use_2to3', False): + # If we run 2to3 we can not do this inplace: + + # Ensure metadata is up-to-date + self.reinitialize_command('build_py', inplace=0) + self.run_command('build_py') + bpy_cmd = self.get_finalized_command("build_py") + build_path = pkg_resources.normalize_path(bpy_cmd.build_lib) + + # Build extensions + self.reinitialize_command('egg_info', egg_base=build_path) + self.run_command('egg_info') + + self.reinitialize_command('build_ext', inplace=0) + self.run_command('build_ext') + + # Fixup egg-link and easy-install.pth + ei_cmd = self.get_finalized_command("egg_info") + self.egg_path = build_path + self.dist.location = build_path + # XXX + self.dist._provider = pkg_resources.PathMetadata( + build_path, ei_cmd.egg_info) + else: + # Without 2to3 inplace works fine: + self.run_command('egg_info') + + # Build extensions in-place + self.reinitialize_command('build_ext', inplace=1) + self.run_command('build_ext') + + self.install_site_py() # ensure that target dir is site-safe + if setuptools.bootstrap_install_from: + self.easy_install(setuptools.bootstrap_install_from) + setuptools.bootstrap_install_from = None + + self.install_namespaces() + + # create an .egg-link in the installation dir, pointing to our egg + log.info("Creating %s (link to %s)", self.egg_link, self.egg_base) + if not self.dry_run: + with open(self.egg_link, "w") as f: + f.write(self.egg_path + "\n" + self.setup_path) + # postprocess the installed distro, fixing up .pth, installing scripts, + # and handling requirements + self.process_distribution(None, self.dist, not self.no_deps) + + def uninstall_link(self): + if os.path.exists(self.egg_link): + log.info("Removing %s (link to %s)", self.egg_link, self.egg_base) + egg_link_file = open(self.egg_link) + contents = [line.rstrip() for line in egg_link_file] + egg_link_file.close() + if contents not in ([self.egg_path], + [self.egg_path, self.setup_path]): + log.warn("Link points to %s: uninstall aborted", contents) + return + if not self.dry_run: + os.unlink(self.egg_link) + if not self.dry_run: + self.update_pth(self.dist) # remove any .pth link to us + if self.distribution.scripts: + # XXX should also check for entry point scripts! + log.warn("Note: you must uninstall or replace scripts manually!") + + def install_egg_scripts(self, dist): + if dist is not self.dist: + # Installing a dependency, so fall back to normal behavior + return easy_install.install_egg_scripts(self, dist) + + # create wrapper scripts in the script dir, pointing to dist.scripts + + # new-style... + self.install_wrapper_scripts(dist) + + # ...and old-style + for script_name in self.distribution.scripts or []: + script_path = os.path.abspath(convert_path(script_name)) + script_name = os.path.basename(script_path) + with io.open(script_path) as strm: + script_text = strm.read() + self.install_script(dist, script_name, script_text, script_path) + + def install_wrapper_scripts(self, dist): + dist = VersionlessRequirement(dist) + return easy_install.install_wrapper_scripts(self, dist) + + +class VersionlessRequirement: + """ + Adapt a pkg_resources.Distribution to simply return the project + name as the 'requirement' so that scripts will work across + multiple versions. + + >>> from pkg_resources import Distribution + >>> dist = Distribution(project_name='foo', version='1.0') + >>> str(dist.as_requirement()) + 'foo==1.0' + >>> adapted_dist = VersionlessRequirement(dist) + >>> str(adapted_dist.as_requirement()) + 'foo' + """ + + def __init__(self, dist): + self.__dist = dist + + def __getattr__(self, name): + return getattr(self.__dist, name) + + def as_requirement(self): + return self.project_name diff --git a/robot/lib/python3.8/site-packages/setuptools/command/dist_info.py b/robot/lib/python3.8/site-packages/setuptools/command/dist_info.py new file mode 100644 index 0000000000000000000000000000000000000000..c45258fa03a3ddd6a73db4514365f8741d16ca86 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/dist_info.py @@ -0,0 +1,36 @@ +""" +Create a dist_info directory +As defined in the wheel specification +""" + +import os + +from distutils.core import Command +from distutils import log + + +class dist_info(Command): + + description = 'create a .dist-info directory' + + user_options = [ + ('egg-base=', 'e', "directory containing .egg-info directories" + " (default: top of the source tree)"), + ] + + def initialize_options(self): + self.egg_base = None + + def finalize_options(self): + pass + + def run(self): + egg_info = self.get_finalized_command('egg_info') + egg_info.egg_base = self.egg_base + egg_info.finalize_options() + egg_info.run() + dist_info_dir = egg_info.egg_info[:-len('.egg-info')] + '.dist-info' + log.info("creating '{}'".format(os.path.abspath(dist_info_dir))) + + bdist_wheel = self.get_finalized_command('bdist_wheel') + bdist_wheel.egg2dist(egg_info.egg_info, dist_info_dir) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/easy_install.py b/robot/lib/python3.8/site-packages/setuptools/command/easy_install.py new file mode 100644 index 0000000000000000000000000000000000000000..1f6839cb3b78fe8d63f709b6ff9abb15bf276b6e --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/easy_install.py @@ -0,0 +1,2402 @@ +#!/usr/bin/env python +""" +Easy Install +------------ + +A tool for doing automatic download/extract/build of distutils-based Python +packages. For detailed documentation, see the accompanying EasyInstall.txt +file, or visit the `EasyInstall home page`__. + +__ https://setuptools.readthedocs.io/en/latest/easy_install.html + +""" + +from glob import glob +from distutils.util import get_platform +from distutils.util import convert_path, subst_vars +from distutils.errors import ( + DistutilsArgError, DistutilsOptionError, + DistutilsError, DistutilsPlatformError, +) +from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS +from distutils import log, dir_util +from distutils.command.build_scripts import first_line_re +from distutils.spawn import find_executable +import sys +import os +import zipimport +import shutil +import tempfile +import zipfile +import re +import stat +import random +import textwrap +import warnings +import site +import struct +import contextlib +import subprocess +import shlex +import io + + +from sysconfig import get_config_vars, get_path + +from setuptools import SetuptoolsDeprecationWarning + +from setuptools.extern import six +from setuptools.extern.six.moves import configparser, map + +from setuptools import Command +from setuptools.sandbox import run_setup +from setuptools.py27compat import rmtree_safe +from setuptools.command import setopt +from setuptools.archive_util import unpack_archive +from setuptools.package_index import ( + PackageIndex, parse_requirement_arg, URL_SCHEME, +) +from setuptools.command import bdist_egg, egg_info +from setuptools.wheel import Wheel +from pkg_resources import ( + yield_lines, normalize_path, resource_string, ensure_directory, + get_distribution, find_distributions, Environment, Requirement, + Distribution, PathMetadata, EggMetadata, WorkingSet, DistributionNotFound, + VersionConflict, DEVELOP_DIST, +) +import pkg_resources.py31compat + +__metaclass__ = type + +# Turn on PEP440Warnings +warnings.filterwarnings("default", category=pkg_resources.PEP440Warning) + +__all__ = [ + 'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg', + 'main', 'get_exe_prefixes', +] + + +def is_64bit(): + return struct.calcsize("P") == 8 + + +def samefile(p1, p2): + """ + Determine if two paths reference the same file. + + Augments os.path.samefile to work on Windows and + suppresses errors if the path doesn't exist. + """ + both_exist = os.path.exists(p1) and os.path.exists(p2) + use_samefile = hasattr(os.path, 'samefile') and both_exist + if use_samefile: + return os.path.samefile(p1, p2) + norm_p1 = os.path.normpath(os.path.normcase(p1)) + norm_p2 = os.path.normpath(os.path.normcase(p2)) + return norm_p1 == norm_p2 + + +if six.PY2: + + def _to_bytes(s): + return s + + def isascii(s): + try: + six.text_type(s, 'ascii') + return True + except UnicodeError: + return False +else: + + def _to_bytes(s): + return s.encode('utf8') + + def isascii(s): + try: + s.encode('ascii') + return True + except UnicodeError: + return False + + +_one_liner = lambda text: textwrap.dedent(text).strip().replace('\n', '; ') + + +class easy_install(Command): + """Manage a download/build/install process""" + description = "Find/get/install Python packages" + command_consumes_arguments = True + + user_options = [ + ('prefix=', None, "installation prefix"), + ("zip-ok", "z", "install package as a zipfile"), + ("multi-version", "m", "make apps have to require() a version"), + ("upgrade", "U", "force upgrade (searches PyPI for latest versions)"), + ("install-dir=", "d", "install package to DIR"), + ("script-dir=", "s", "install scripts to DIR"), + ("exclude-scripts", "x", "Don't install scripts"), + ("always-copy", "a", "Copy all needed packages to install dir"), + ("index-url=", "i", "base URL of Python Package Index"), + ("find-links=", "f", "additional URL(s) to search for packages"), + ("build-directory=", "b", + "download/extract/build in DIR; keep the results"), + ('optimize=', 'O', + "also compile with optimization: -O1 for \"python -O\", " + "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), + ('record=', None, + "filename in which to record list of installed files"), + ('always-unzip', 'Z', "don't install as a zipfile, no matter what"), + ('site-dirs=', 'S', "list of directories where .pth files work"), + ('editable', 'e', "Install specified packages in editable form"), + ('no-deps', 'N', "don't install dependencies"), + ('allow-hosts=', 'H', "pattern(s) that hostnames must match"), + ('local-snapshots-ok', 'l', + "allow building eggs from local checkouts"), + ('version', None, "print version information and exit"), + ('install-layout=', None, "installation layout to choose (known values: deb)"), + ('force-installation-into-system-dir', '0', "force installation into /usr"), + ('no-find-links', None, + "Don't load find-links defined in packages being installed") + ] + boolean_options = [ + 'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy', + 'editable', + 'no-deps', 'local-snapshots-ok', 'version', 'force-installation-into-system-dir' + ] + + if site.ENABLE_USER_SITE: + help_msg = "install in user site-package '%s'" % site.USER_SITE + user_options.append(('user', None, help_msg)) + boolean_options.append('user') + + negative_opt = {'always-unzip': 'zip-ok'} + create_index = PackageIndex + + def initialize_options(self): + # the --user option seems to be an opt-in one, + # so the default should be False. + self.user = 0 + self.zip_ok = self.local_snapshots_ok = None + self.install_dir = self.script_dir = self.exclude_scripts = None + self.index_url = None + self.find_links = None + self.build_directory = None + self.args = None + self.optimize = self.record = None + self.upgrade = self.always_copy = self.multi_version = None + self.editable = self.no_deps = self.allow_hosts = None + self.root = self.prefix = self.no_report = None + self.version = None + self.install_purelib = None # for pure module distributions + self.install_platlib = None # non-pure (dists w/ extensions) + self.install_headers = None # for C/C++ headers + self.install_lib = None # set to either purelib or platlib + self.install_scripts = None + self.install_data = None + self.install_base = None + self.install_platbase = None + if site.ENABLE_USER_SITE: + self.install_userbase = site.USER_BASE + self.install_usersite = site.USER_SITE + else: + self.install_userbase = None + self.install_usersite = None + self.no_find_links = None + + # Options not specifiable via command line + self.package_index = None + self.pth_file = self.always_copy_from = None + self.site_dirs = None + self.installed_projects = {} + self.sitepy_installed = False + # enable custom installation, known values: deb + self.install_layout = None + self.force_installation_into_system_dir = None + self.multiarch = None + + # Always read easy_install options, even if we are subclassed, or have + # an independent instance created. This ensures that defaults will + # always come from the standard configuration file(s)' "easy_install" + # section, even if this is a "develop" or "install" command, or some + # other embedding. + self._dry_run = None + self.verbose = self.distribution.verbose + self.distribution._set_command_options( + self, self.distribution.get_option_dict('easy_install') + ) + + def delete_blockers(self, blockers): + extant_blockers = ( + filename for filename in blockers + if os.path.exists(filename) or os.path.islink(filename) + ) + list(map(self._delete_path, extant_blockers)) + + def _delete_path(self, path): + log.info("Deleting %s", path) + if self.dry_run: + return + + is_tree = os.path.isdir(path) and not os.path.islink(path) + remover = rmtree if is_tree else os.unlink + remover(path) + + @staticmethod + def _render_version(): + """ + Render the Setuptools version and installation details, then exit. + """ + ver = '{}.{}'.format(*sys.version_info) + dist = get_distribution('setuptools') + tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})' + print(tmpl.format(**locals())) + raise SystemExit() + + def finalize_options(self): + self.version and self._render_version() + + py_version = sys.version.split()[0] + prefix, exec_prefix = get_config_vars('prefix', 'exec_prefix') + + self.config_vars = { + 'dist_name': self.distribution.get_name(), + 'dist_version': self.distribution.get_version(), + 'dist_fullname': self.distribution.get_fullname(), + 'py_version': py_version, + 'py_version_short': py_version[0:3], + 'py_version_nodot': py_version[0] + py_version[2], + 'sys_prefix': prefix, + 'prefix': prefix, + 'sys_exec_prefix': exec_prefix, + 'exec_prefix': exec_prefix, + # Only python 3.2+ has abiflags + 'abiflags': getattr(sys, 'abiflags', ''), + } + + if site.ENABLE_USER_SITE: + self.config_vars['userbase'] = self.install_userbase + self.config_vars['usersite'] = self.install_usersite + + self._fix_install_dir_for_user_site() + + self.expand_basedirs() + self.expand_dirs() + + if self.install_layout: + if not self.install_layout.lower() in ['deb']: + raise DistutilsOptionError("unknown value for --install-layout") + self.install_layout = self.install_layout.lower() + + import sysconfig + if sys.version_info[:2] >= (3, 3): + self.multiarch = sysconfig.get_config_var('MULTIARCH') + + self._expand( + 'install_dir', 'script_dir', 'build_directory', + 'site_dirs', + ) + # If a non-default installation directory was specified, default the + # script directory to match it. + if self.script_dir is None: + self.script_dir = self.install_dir + + if self.no_find_links is None: + self.no_find_links = False + + # Let install_dir get set by install_lib command, which in turn + # gets its info from the install command, and takes into account + # --prefix and --home and all that other crud. + self.set_undefined_options( + 'install_lib', ('install_dir', 'install_dir') + ) + # Likewise, set default script_dir from 'install_scripts.install_dir' + self.set_undefined_options( + 'install_scripts', ('install_dir', 'script_dir') + ) + + if self.user and self.install_purelib: + self.install_dir = self.install_purelib + self.script_dir = self.install_scripts + + if self.prefix == '/usr' and not self.force_installation_into_system_dir: + raise DistutilsOptionError("""installation into /usr + +Trying to install into the system managed parts of the file system. Please +consider to install to another location, or use the option +--force-installation-into-system-dir to overwrite this warning. +""") + + # default --record from the install command + self.set_undefined_options('install', ('record', 'record')) + # Should this be moved to the if statement below? It's not used + # elsewhere + normpath = map(normalize_path, sys.path) + self.all_site_dirs = get_site_dirs() + if self.site_dirs is not None: + site_dirs = [ + os.path.expanduser(s.strip()) for s in + self.site_dirs.split(',') + ] + for d in site_dirs: + if not os.path.isdir(d): + log.warn("%s (in --site-dirs) does not exist", d) + elif normalize_path(d) not in normpath: + raise DistutilsOptionError( + d + " (in --site-dirs) is not on sys.path" + ) + else: + self.all_site_dirs.append(normalize_path(d)) + if not self.editable: + self.check_site_dir() + self.index_url = self.index_url or "https://pypi.org/simple/" + self.shadow_path = self.all_site_dirs[:] + for path_item in self.install_dir, normalize_path(self.script_dir): + if path_item not in self.shadow_path: + self.shadow_path.insert(0, path_item) + + if self.allow_hosts is not None: + hosts = [s.strip() for s in self.allow_hosts.split(',')] + else: + hosts = ['*'] + if self.package_index is None: + self.package_index = self.create_index( + self.index_url, search_path=self.shadow_path, hosts=hosts, + ) + self.local_index = Environment(self.shadow_path + sys.path) + + if self.find_links is not None: + if isinstance(self.find_links, six.string_types): + self.find_links = self.find_links.split() + else: + self.find_links = [] + if self.local_snapshots_ok: + self.package_index.scan_egg_links(self.shadow_path + sys.path) + if not self.no_find_links: + self.package_index.add_find_links(self.find_links) + self.set_undefined_options('install_lib', ('optimize', 'optimize')) + if not isinstance(self.optimize, int): + try: + self.optimize = int(self.optimize) + if not (0 <= self.optimize <= 2): + raise ValueError + except ValueError: + raise DistutilsOptionError("--optimize must be 0, 1, or 2") + + if self.editable and not self.build_directory: + raise DistutilsArgError( + "Must specify a build directory (-b) when using --editable" + ) + if not self.args: + raise DistutilsArgError( + "No urls, filenames, or requirements specified (see --help)") + + self.outputs = [] + + def _fix_install_dir_for_user_site(self): + """ + Fix the install_dir if "--user" was used. + """ + if not self.user or not site.ENABLE_USER_SITE: + return + + self.create_home_path() + if self.install_userbase is None: + msg = "User base directory is not specified" + raise DistutilsPlatformError(msg) + self.install_base = self.install_platbase = self.install_userbase + scheme_name = os.name.replace('posix', 'unix') + '_user' + self.select_scheme(scheme_name) + + def _expand_attrs(self, attrs): + for attr in attrs: + val = getattr(self, attr) + if val is not None: + if os.name == 'posix' or os.name == 'nt': + val = os.path.expanduser(val) + val = subst_vars(val, self.config_vars) + setattr(self, attr, val) + + def expand_basedirs(self): + """Calls `os.path.expanduser` on install_base, install_platbase and + root.""" + self._expand_attrs(['install_base', 'install_platbase', 'root']) + + def expand_dirs(self): + """Calls `os.path.expanduser` on install dirs.""" + dirs = [ + 'install_purelib', + 'install_platlib', + 'install_lib', + 'install_headers', + 'install_scripts', + 'install_data', + ] + self._expand_attrs(dirs) + + def run(self, show_deprecation=True): + if show_deprecation: + self.announce( + "WARNING: The easy_install command is deprecated " + "and will be removed in a future version." + , log.WARN, + ) + if self.verbose != self.distribution.verbose: + log.set_verbosity(self.verbose) + try: + for spec in self.args: + self.easy_install(spec, not self.no_deps) + if self.record: + outputs = list(sorted(self.outputs)) + if self.root: # strip any package prefix + root_len = len(self.root) + for counter in range(len(outputs)): + outputs[counter] = outputs[counter][root_len:] + from distutils import file_util + + self.execute( + file_util.write_file, (self.record, outputs), + "writing list of installed files to '%s'" % + self.record + ) + self.warn_deprecated_options() + finally: + log.set_verbosity(self.distribution.verbose) + + def pseudo_tempname(self): + """Return a pseudo-tempname base in the install directory. + This code is intentionally naive; if a malicious party can write to + the target directory you're already in deep doodoo. + """ + try: + pid = os.getpid() + except Exception: + pid = random.randint(0, sys.maxsize) + return os.path.join(self.install_dir, "test-easy-install-%s" % pid) + + def warn_deprecated_options(self): + pass + + def check_site_dir(self): + """Verify that self.install_dir is .pth-capable dir, if needed""" + + instdir = normalize_path(self.install_dir) + pth_file = os.path.join(instdir, 'easy-install.pth') + + # Is it a configured, PYTHONPATH, implicit, or explicit site dir? + is_site_dir = instdir in self.all_site_dirs + + if not is_site_dir and not self.multi_version: + # No? Then directly test whether it does .pth file processing + is_site_dir = self.check_pth_processing() + else: + # make sure we can write to target dir + testfile = self.pseudo_tempname() + '.write-test' + test_exists = os.path.exists(testfile) + try: + if test_exists: + os.unlink(testfile) + open(testfile, 'w').close() + os.unlink(testfile) + except (OSError, IOError): + self.cant_write_to_target() + + if not is_site_dir and not self.multi_version: + # Can't install non-multi to non-site dir + raise DistutilsError(self.no_default_version_msg()) + + if is_site_dir: + if self.pth_file is None: + self.pth_file = PthDistributions(pth_file, self.all_site_dirs) + else: + self.pth_file = None + + if instdir not in map(normalize_path, _pythonpath()): + # only PYTHONPATH dirs need a site.py, so pretend it's there + self.sitepy_installed = True + elif self.multi_version and not os.path.exists(pth_file): + self.sitepy_installed = True # don't need site.py in this case + self.pth_file = None # and don't create a .pth file + self.install_dir = instdir + + __cant_write_msg = textwrap.dedent(""" + can't create or remove files in install directory + + The following error occurred while trying to add or remove files in the + installation directory: + + %s + + The installation directory you specified (via --install-dir, --prefix, or + the distutils default setting) was: + + %s + """).lstrip() + + __not_exists_id = textwrap.dedent(""" + This directory does not currently exist. Please create it and try again, or + choose a different installation directory (using the -d or --install-dir + option). + """).lstrip() + + __access_msg = textwrap.dedent(""" + Perhaps your account does not have write access to this directory? If the + installation directory is a system-owned directory, you may need to sign in + as the administrator or "root" account. If you do not have administrative + access to this machine, you may wish to choose a different installation + directory, preferably one that is listed in your PYTHONPATH environment + variable. + + For information on other options, you may wish to consult the + documentation at: + + https://setuptools.readthedocs.io/en/latest/easy_install.html + + Please make the appropriate changes for your system and try again. + """).lstrip() + + def cant_write_to_target(self): + msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,) + + if not os.path.exists(self.install_dir): + msg += '\n' + self.__not_exists_id + else: + msg += '\n' + self.__access_msg + raise DistutilsError(msg) + + def check_pth_processing(self): + """Empirically verify whether .pth files are supported in inst. dir""" + instdir = self.install_dir + log.info("Checking .pth file support in %s", instdir) + pth_file = self.pseudo_tempname() + ".pth" + ok_file = pth_file + '.ok' + ok_exists = os.path.exists(ok_file) + tmpl = _one_liner(""" + import os + f = open({ok_file!r}, 'w') + f.write('OK') + f.close() + """) + '\n' + try: + if ok_exists: + os.unlink(ok_file) + dirname = os.path.dirname(ok_file) + pkg_resources.py31compat.makedirs(dirname, exist_ok=True) + f = open(pth_file, 'w') + except (OSError, IOError): + self.cant_write_to_target() + else: + try: + f.write(tmpl.format(**locals())) + f.close() + f = None + executable = sys.executable + if os.name == 'nt': + dirname, basename = os.path.split(executable) + alt = os.path.join(dirname, 'pythonw.exe') + use_alt = ( + basename.lower() == 'python.exe' and + os.path.exists(alt) + ) + if use_alt: + # use pythonw.exe to avoid opening a console window + executable = alt + + from distutils.spawn import spawn + + spawn([executable, '-E', '-c', 'pass'], 0) + + if os.path.exists(ok_file): + log.info( + "TEST PASSED: %s appears to support .pth files", + instdir + ) + return True + finally: + if f: + f.close() + if os.path.exists(ok_file): + os.unlink(ok_file) + if os.path.exists(pth_file): + os.unlink(pth_file) + if not self.multi_version: + log.warn("TEST FAILED: %s does NOT support .pth files", instdir) + return False + + def install_egg_scripts(self, dist): + """Write all the scripts for `dist`, unless scripts are excluded""" + if not self.exclude_scripts and dist.metadata_isdir('scripts'): + for script_name in dist.metadata_listdir('scripts'): + if dist.metadata_isdir('scripts/' + script_name): + # The "script" is a directory, likely a Python 3 + # __pycache__ directory, so skip it. + continue + self.install_script( + dist, script_name, + dist.get_metadata('scripts/' + script_name) + ) + self.install_wrapper_scripts(dist) + + def add_output(self, path): + if os.path.isdir(path): + for base, dirs, files in os.walk(path): + for filename in files: + self.outputs.append(os.path.join(base, filename)) + else: + self.outputs.append(path) + + def not_editable(self, spec): + if self.editable: + raise DistutilsArgError( + "Invalid argument %r: you can't use filenames or URLs " + "with --editable (except via the --find-links option)." + % (spec,) + ) + + def check_editable(self, spec): + if not self.editable: + return + + if os.path.exists(os.path.join(self.build_directory, spec.key)): + raise DistutilsArgError( + "%r already exists in %s; can't do a checkout there" % + (spec.key, self.build_directory) + ) + + @contextlib.contextmanager + def _tmpdir(self): + tmpdir = tempfile.mkdtemp(prefix=u"easy_install-") + try: + # cast to str as workaround for #709 and #710 and #712 + yield str(tmpdir) + finally: + os.path.exists(tmpdir) and rmtree(rmtree_safe(tmpdir)) + + def easy_install(self, spec, deps=False): + if not self.editable: + self.install_site_py() + + with self._tmpdir() as tmpdir: + if not isinstance(spec, Requirement): + if URL_SCHEME(spec): + # It's a url, download it to tmpdir and process + self.not_editable(spec) + dl = self.package_index.download(spec, tmpdir) + return self.install_item(None, dl, tmpdir, deps, True) + + elif os.path.exists(spec): + # Existing file or directory, just process it directly + self.not_editable(spec) + return self.install_item(None, spec, tmpdir, deps, True) + else: + spec = parse_requirement_arg(spec) + + self.check_editable(spec) + dist = self.package_index.fetch_distribution( + spec, tmpdir, self.upgrade, self.editable, + not self.always_copy, self.local_index + ) + if dist is None: + msg = "Could not find suitable distribution for %r" % spec + if self.always_copy: + msg += " (--always-copy skips system and development eggs)" + raise DistutilsError(msg) + elif dist.precedence == DEVELOP_DIST: + # .egg-info dists don't need installing, just process deps + self.process_distribution(spec, dist, deps, "Using") + return dist + else: + return self.install_item(spec, dist.location, tmpdir, deps) + + def install_item(self, spec, download, tmpdir, deps, install_needed=False): + + # Installation is also needed if file in tmpdir or is not an egg + install_needed = install_needed or self.always_copy + install_needed = install_needed or os.path.dirname(download) == tmpdir + install_needed = install_needed or not download.endswith('.egg') + install_needed = install_needed or ( + self.always_copy_from is not None and + os.path.dirname(normalize_path(download)) == + normalize_path(self.always_copy_from) + ) + + if spec and not install_needed: + # at this point, we know it's a local .egg, we just don't know if + # it's already installed. + for dist in self.local_index[spec.project_name]: + if dist.location == download: + break + else: + install_needed = True # it's not in the local index + + log.info("Processing %s", os.path.basename(download)) + + if install_needed: + dists = self.install_eggs(spec, download, tmpdir) + for dist in dists: + self.process_distribution(spec, dist, deps) + else: + dists = [self.egg_distribution(download)] + self.process_distribution(spec, dists[0], deps, "Using") + + if spec is not None: + for dist in dists: + if dist in spec: + return dist + + def select_scheme(self, name): + """Sets the install directories by applying the install schemes.""" + # it's the caller's problem if they supply a bad name! + scheme = INSTALL_SCHEMES[name] + for key in SCHEME_KEYS: + attrname = 'install_' + key + if getattr(self, attrname) is None: + setattr(self, attrname, scheme[key]) + + def process_distribution(self, requirement, dist, deps=True, *info): + self.update_pth(dist) + self.package_index.add(dist) + if dist in self.local_index[dist.key]: + self.local_index.remove(dist) + self.local_index.add(dist) + self.install_egg_scripts(dist) + self.installed_projects[dist.key] = dist + log.info(self.installation_report(requirement, dist, *info)) + if (dist.has_metadata('dependency_links.txt') and + not self.no_find_links): + self.package_index.add_find_links( + dist.get_metadata_lines('dependency_links.txt') + ) + if not deps and not self.always_copy: + return + elif requirement is not None and dist.key != requirement.key: + log.warn("Skipping dependencies for %s", dist) + return # XXX this is not the distribution we were looking for + elif requirement is None or dist not in requirement: + # if we wound up with a different version, resolve what we've got + distreq = dist.as_requirement() + requirement = Requirement(str(distreq)) + log.info("Processing dependencies for %s", requirement) + try: + distros = WorkingSet([]).resolve( + [requirement], self.local_index, self.easy_install + ) + except DistributionNotFound as e: + raise DistutilsError(str(e)) + except VersionConflict as e: + raise DistutilsError(e.report()) + if self.always_copy or self.always_copy_from: + # Force all the relevant distros to be copied or activated + for dist in distros: + if dist.key not in self.installed_projects: + self.easy_install(dist.as_requirement()) + log.info("Finished processing dependencies for %s", requirement) + + def should_unzip(self, dist): + if self.zip_ok is not None: + return not self.zip_ok + if dist.has_metadata('not-zip-safe'): + return True + if not dist.has_metadata('zip-safe'): + return True + return False + + def maybe_move(self, spec, dist_filename, setup_base): + dst = os.path.join(self.build_directory, spec.key) + if os.path.exists(dst): + msg = ( + "%r already exists in %s; build directory %s will not be kept" + ) + log.warn(msg, spec.key, self.build_directory, setup_base) + return setup_base + if os.path.isdir(dist_filename): + setup_base = dist_filename + else: + if os.path.dirname(dist_filename) == setup_base: + os.unlink(dist_filename) # get it out of the tmp dir + contents = os.listdir(setup_base) + if len(contents) == 1: + dist_filename = os.path.join(setup_base, contents[0]) + if os.path.isdir(dist_filename): + # if the only thing there is a directory, move it instead + setup_base = dist_filename + ensure_directory(dst) + shutil.move(setup_base, dst) + return dst + + def install_wrapper_scripts(self, dist): + if self.exclude_scripts: + return + for args in ScriptWriter.best().get_args(dist): + self.write_script(*args) + + def install_script(self, dist, script_name, script_text, dev_path=None): + """Generate a legacy script wrapper and install it""" + spec = str(dist.as_requirement()) + is_script = is_python_script(script_text, script_name) + + if is_script: + body = self._load_template(dev_path) % locals() + script_text = ScriptWriter.get_header(script_text) + body + self.write_script(script_name, _to_bytes(script_text), 'b') + + @staticmethod + def _load_template(dev_path): + """ + There are a couple of template scripts in the package. This + function loads one of them and prepares it for use. + """ + # See https://github.com/pypa/setuptools/issues/134 for info + # on script file naming and downstream issues with SVR4 + name = 'script.tmpl' + if dev_path: + name = name.replace('.tmpl', ' (dev).tmpl') + + raw_bytes = resource_string('setuptools', name) + return raw_bytes.decode('utf-8') + + def write_script(self, script_name, contents, mode="t", blockers=()): + """Write an executable file to the scripts directory""" + self.delete_blockers( # clean up old .py/.pyw w/o a script + [os.path.join(self.script_dir, x) for x in blockers] + ) + log.info("Installing %s script to %s", script_name, self.script_dir) + target = os.path.join(self.script_dir, script_name) + self.add_output(target) + + if self.dry_run: + return + + mask = current_umask() + ensure_directory(target) + if os.path.exists(target): + os.unlink(target) + with open(target, "w" + mode) as f: + f.write(contents) + chmod(target, 0o777 - mask) + + def install_eggs(self, spec, dist_filename, tmpdir): + # .egg dirs or files are already built, so just return them + if dist_filename.lower().endswith('.egg'): + return [self.install_egg(dist_filename, tmpdir)] + elif dist_filename.lower().endswith('.exe'): + return [self.install_exe(dist_filename, tmpdir)] + elif dist_filename.lower().endswith('.whl'): + return [self.install_wheel(dist_filename, tmpdir)] + + # Anything else, try to extract and build + setup_base = tmpdir + if os.path.isfile(dist_filename) and not dist_filename.endswith('.py'): + unpack_archive(dist_filename, tmpdir, self.unpack_progress) + elif os.path.isdir(dist_filename): + setup_base = os.path.abspath(dist_filename) + + if (setup_base.startswith(tmpdir) # something we downloaded + and self.build_directory and spec is not None): + setup_base = self.maybe_move(spec, dist_filename, setup_base) + + # Find the setup.py file + setup_script = os.path.join(setup_base, 'setup.py') + + if not os.path.exists(setup_script): + setups = glob(os.path.join(setup_base, '*', 'setup.py')) + if not setups: + raise DistutilsError( + "Couldn't find a setup script in %s" % + os.path.abspath(dist_filename) + ) + if len(setups) > 1: + raise DistutilsError( + "Multiple setup scripts in %s" % + os.path.abspath(dist_filename) + ) + setup_script = setups[0] + + # Now run it, and return the result + if self.editable: + log.info(self.report_editable(spec, setup_script)) + return [] + else: + return self.build_and_install(setup_script, setup_base) + + def egg_distribution(self, egg_path): + if os.path.isdir(egg_path): + metadata = PathMetadata(egg_path, os.path.join(egg_path, + 'EGG-INFO')) + else: + metadata = EggMetadata(zipimport.zipimporter(egg_path)) + return Distribution.from_filename(egg_path, metadata=metadata) + + def install_egg(self, egg_path, tmpdir): + destination = os.path.join( + self.install_dir, + os.path.basename(egg_path), + ) + destination = os.path.abspath(destination) + if not self.dry_run: + ensure_directory(destination) + + dist = self.egg_distribution(egg_path) + if not samefile(egg_path, destination): + if os.path.isdir(destination) and not os.path.islink(destination): + dir_util.remove_tree(destination, dry_run=self.dry_run) + elif os.path.exists(destination): + self.execute( + os.unlink, + (destination,), + "Removing " + destination, + ) + try: + new_dist_is_zipped = False + if os.path.isdir(egg_path): + if egg_path.startswith(tmpdir): + f, m = shutil.move, "Moving" + else: + f, m = shutil.copytree, "Copying" + elif self.should_unzip(dist): + self.mkpath(destination) + f, m = self.unpack_and_compile, "Extracting" + else: + new_dist_is_zipped = True + if egg_path.startswith(tmpdir): + f, m = shutil.move, "Moving" + else: + f, m = shutil.copy2, "Copying" + self.execute( + f, + (egg_path, destination), + (m + " %s to %s") % ( + os.path.basename(egg_path), + os.path.dirname(destination) + ), + ) + update_dist_caches( + destination, + fix_zipimporter_caches=new_dist_is_zipped, + ) + except Exception: + update_dist_caches(destination, fix_zipimporter_caches=False) + raise + + self.add_output(destination) + return self.egg_distribution(destination) + + def install_exe(self, dist_filename, tmpdir): + # See if it's valid, get data + cfg = extract_wininst_cfg(dist_filename) + if cfg is None: + raise DistutilsError( + "%s is not a valid distutils Windows .exe" % dist_filename + ) + # Create a dummy distribution object until we build the real distro + dist = Distribution( + None, + project_name=cfg.get('metadata', 'name'), + version=cfg.get('metadata', 'version'), platform=get_platform(), + ) + + # Convert the .exe to an unpacked egg + egg_path = os.path.join(tmpdir, dist.egg_name() + '.egg') + dist.location = egg_path + egg_tmp = egg_path + '.tmp' + _egg_info = os.path.join(egg_tmp, 'EGG-INFO') + pkg_inf = os.path.join(_egg_info, 'PKG-INFO') + ensure_directory(pkg_inf) # make sure EGG-INFO dir exists + dist._provider = PathMetadata(egg_tmp, _egg_info) # XXX + self.exe_to_egg(dist_filename, egg_tmp) + + # Write EGG-INFO/PKG-INFO + if not os.path.exists(pkg_inf): + f = open(pkg_inf, 'w') + f.write('Metadata-Version: 1.0\n') + for k, v in cfg.items('metadata'): + if k != 'target_version': + f.write('%s: %s\n' % (k.replace('_', '-').title(), v)) + f.close() + script_dir = os.path.join(_egg_info, 'scripts') + # delete entry-point scripts to avoid duping + self.delete_blockers([ + os.path.join(script_dir, args[0]) + for args in ScriptWriter.get_args(dist) + ]) + # Build .egg file from tmpdir + bdist_egg.make_zipfile( + egg_path, egg_tmp, verbose=self.verbose, dry_run=self.dry_run, + ) + # install the .egg + return self.install_egg(egg_path, tmpdir) + + def exe_to_egg(self, dist_filename, egg_tmp): + """Extract a bdist_wininst to the directories an egg would use""" + # Check for .pth file and set up prefix translations + prefixes = get_exe_prefixes(dist_filename) + to_compile = [] + native_libs = [] + top_level = {} + + def process(src, dst): + s = src.lower() + for old, new in prefixes: + if s.startswith(old): + src = new + src[len(old):] + parts = src.split('/') + dst = os.path.join(egg_tmp, *parts) + dl = dst.lower() + if dl.endswith('.pyd') or dl.endswith('.dll'): + parts[-1] = bdist_egg.strip_module(parts[-1]) + top_level[os.path.splitext(parts[0])[0]] = 1 + native_libs.append(src) + elif dl.endswith('.py') and old != 'SCRIPTS/': + top_level[os.path.splitext(parts[0])[0]] = 1 + to_compile.append(dst) + return dst + if not src.endswith('.pth'): + log.warn("WARNING: can't process %s", src) + return None + + # extract, tracking .pyd/.dll->native_libs and .py -> to_compile + unpack_archive(dist_filename, egg_tmp, process) + stubs = [] + for res in native_libs: + if res.lower().endswith('.pyd'): # create stubs for .pyd's + parts = res.split('/') + resource = parts[-1] + parts[-1] = bdist_egg.strip_module(parts[-1]) + '.py' + pyfile = os.path.join(egg_tmp, *parts) + to_compile.append(pyfile) + stubs.append(pyfile) + bdist_egg.write_stub(resource, pyfile) + self.byte_compile(to_compile) # compile .py's + bdist_egg.write_safety_flag( + os.path.join(egg_tmp, 'EGG-INFO'), + bdist_egg.analyze_egg(egg_tmp, stubs)) # write zip-safety flag + + for name in 'top_level', 'native_libs': + if locals()[name]: + txt = os.path.join(egg_tmp, 'EGG-INFO', name + '.txt') + if not os.path.exists(txt): + f = open(txt, 'w') + f.write('\n'.join(locals()[name]) + '\n') + f.close() + + def install_wheel(self, wheel_path, tmpdir): + wheel = Wheel(wheel_path) + assert wheel.is_compatible() + destination = os.path.join(self.install_dir, wheel.egg_name()) + destination = os.path.abspath(destination) + if not self.dry_run: + ensure_directory(destination) + if os.path.isdir(destination) and not os.path.islink(destination): + dir_util.remove_tree(destination, dry_run=self.dry_run) + elif os.path.exists(destination): + self.execute( + os.unlink, + (destination,), + "Removing " + destination, + ) + try: + self.execute( + wheel.install_as_egg, + (destination,), + ("Installing %s to %s") % ( + os.path.basename(wheel_path), + os.path.dirname(destination) + ), + ) + finally: + update_dist_caches(destination, fix_zipimporter_caches=False) + self.add_output(destination) + return self.egg_distribution(destination) + + __mv_warning = textwrap.dedent(""" + Because this distribution was installed --multi-version, before you can + import modules from this package in an application, you will need to + 'import pkg_resources' and then use a 'require()' call similar to one of + these examples, in order to select the desired version: + + pkg_resources.require("%(name)s") # latest installed version + pkg_resources.require("%(name)s==%(version)s") # this exact version + pkg_resources.require("%(name)s>=%(version)s") # this version or higher + """).lstrip() + + __id_warning = textwrap.dedent(""" + Note also that the installation directory must be on sys.path at runtime for + this to work. (e.g. by being the application's script directory, by being on + PYTHONPATH, or by being added to sys.path by your code.) + """) + + def installation_report(self, req, dist, what="Installed"): + """Helpful installation message for display to package users""" + msg = "\n%(what)s %(eggloc)s%(extras)s" + if self.multi_version and not self.no_report: + msg += '\n' + self.__mv_warning + if self.install_dir not in map(normalize_path, sys.path): + msg += '\n' + self.__id_warning + + eggloc = dist.location + name = dist.project_name + version = dist.version + extras = '' # TODO: self.report_extras(req, dist) + return msg % locals() + + __editable_msg = textwrap.dedent(""" + Extracted editable version of %(spec)s to %(dirname)s + + If it uses setuptools in its setup script, you can activate it in + "development" mode by going to that directory and running:: + + %(python)s setup.py develop + + See the setuptools documentation for the "develop" command for more info. + """).lstrip() + + def report_editable(self, spec, setup_script): + dirname = os.path.dirname(setup_script) + python = sys.executable + return '\n' + self.__editable_msg % locals() + + def run_setup(self, setup_script, setup_base, args): + sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg) + sys.modules.setdefault('distutils.command.egg_info', egg_info) + + args = list(args) + if self.verbose > 2: + v = 'v' * (self.verbose - 1) + args.insert(0, '-' + v) + elif self.verbose < 2: + args.insert(0, '-q') + if self.dry_run: + args.insert(0, '-n') + log.info( + "Running %s %s", setup_script[len(setup_base) + 1:], ' '.join(args) + ) + try: + run_setup(setup_script, args) + except SystemExit as v: + raise DistutilsError("Setup script exited with %s" % (v.args[0],)) + + def build_and_install(self, setup_script, setup_base): + args = ['bdist_egg', '--dist-dir'] + + dist_dir = tempfile.mkdtemp( + prefix='egg-dist-tmp-', dir=os.path.dirname(setup_script) + ) + try: + self._set_fetcher_options(os.path.dirname(setup_script)) + args.append(dist_dir) + + self.run_setup(setup_script, setup_base, args) + all_eggs = Environment([dist_dir]) + eggs = [] + for key in all_eggs: + for dist in all_eggs[key]: + eggs.append(self.install_egg(dist.location, setup_base)) + if not eggs and not self.dry_run: + log.warn("No eggs found in %s (setup script problem?)", + dist_dir) + return eggs + finally: + rmtree(dist_dir) + log.set_verbosity(self.verbose) # restore our log verbosity + + def _set_fetcher_options(self, base): + """ + When easy_install is about to run bdist_egg on a source dist, that + source dist might have 'setup_requires' directives, requiring + additional fetching. Ensure the fetcher options given to easy_install + are available to that command as well. + """ + # find the fetch options from easy_install and write them out + # to the setup.cfg file. + ei_opts = self.distribution.get_option_dict('easy_install').copy() + fetch_directives = ( + 'find_links', 'site_dirs', 'index_url', 'optimize', 'allow_hosts', + ) + fetch_options = {} + for key, val in ei_opts.items(): + if key not in fetch_directives: + continue + fetch_options[key.replace('_', '-')] = val[1] + # create a settings dictionary suitable for `edit_config` + settings = dict(easy_install=fetch_options) + cfg_filename = os.path.join(base, 'setup.cfg') + setopt.edit_config(cfg_filename, settings) + + def update_pth(self, dist): + if self.pth_file is None: + return + + for d in self.pth_file[dist.key]: # drop old entries + if self.multi_version or d.location != dist.location: + log.info("Removing %s from easy-install.pth file", d) + self.pth_file.remove(d) + if d.location in self.shadow_path: + self.shadow_path.remove(d.location) + + if not self.multi_version: + if dist.location in self.pth_file.paths: + log.info( + "%s is already the active version in easy-install.pth", + dist, + ) + else: + log.info("Adding %s to easy-install.pth file", dist) + self.pth_file.add(dist) # add new entry + if dist.location not in self.shadow_path: + self.shadow_path.append(dist.location) + + if not self.dry_run: + + self.pth_file.save() + + if dist.key == 'setuptools': + # Ensure that setuptools itself never becomes unavailable! + # XXX should this check for latest version? + filename = os.path.join(self.install_dir, 'setuptools.pth') + if os.path.islink(filename): + os.unlink(filename) + f = open(filename, 'wt') + f.write(self.pth_file.make_relative(dist.location) + '\n') + f.close() + + def unpack_progress(self, src, dst): + # Progress filter for unpacking + log.debug("Unpacking %s to %s", src, dst) + return dst # only unpack-and-compile skips files for dry run + + def unpack_and_compile(self, egg_path, destination): + to_compile = [] + to_chmod = [] + + def pf(src, dst): + if dst.endswith('.py') and not src.startswith('EGG-INFO/'): + to_compile.append(dst) + elif dst.endswith('.dll') or dst.endswith('.so'): + to_chmod.append(dst) + self.unpack_progress(src, dst) + return not self.dry_run and dst or None + + unpack_archive(egg_path, destination, pf) + self.byte_compile(to_compile) + if not self.dry_run: + for f in to_chmod: + mode = ((os.stat(f)[stat.ST_MODE]) | 0o555) & 0o7755 + chmod(f, mode) + + def byte_compile(self, to_compile): + if sys.dont_write_bytecode: + return + + from distutils.util import byte_compile + + try: + # try to make the byte compile messages quieter + log.set_verbosity(self.verbose - 1) + + byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run) + if self.optimize: + byte_compile( + to_compile, optimize=self.optimize, force=1, + dry_run=self.dry_run, + ) + finally: + log.set_verbosity(self.verbose) # restore original verbosity + + __no_default_msg = textwrap.dedent(""" + bad install directory or PYTHONPATH + + You are attempting to install a package to a directory that is not + on PYTHONPATH and which Python does not read ".pth" files from. The + installation directory you specified (via --install-dir, --prefix, or + the distutils default setting) was: + + %s + + and your PYTHONPATH environment variable currently contains: + + %r + + Here are some of your options for correcting the problem: + + * You can choose a different installation directory, i.e., one that is + on PYTHONPATH or supports .pth files + + * You can add the installation directory to the PYTHONPATH environment + variable. (It must then also be on PYTHONPATH whenever you run + Python and want to use the package(s) you are installing.) + + * You can set up the installation directory to support ".pth" files by + using one of the approaches described here: + + https://setuptools.readthedocs.io/en/latest/easy_install.html#custom-installation-locations + + + Please make the appropriate changes for your system and try again.""").lstrip() + + def no_default_version_msg(self): + template = self.__no_default_msg + return template % (self.install_dir, os.environ.get('PYTHONPATH', '')) + + def install_site_py(self): + """Make sure there's a site.py in the target dir, if needed""" + + if self.sitepy_installed: + return # already did it, or don't need to + + sitepy = os.path.join(self.install_dir, "site.py") + source = resource_string("setuptools", "site-patch.py") + source = source.decode('utf-8') + current = "" + + if os.path.exists(sitepy): + log.debug("Checking existing site.py in %s", self.install_dir) + with io.open(sitepy) as strm: + current = strm.read() + + if not current.startswith('def __boot():'): + raise DistutilsError( + "%s is not a setuptools-generated site.py; please" + " remove it." % sitepy + ) + + if current != source: + log.info("Creating %s", sitepy) + if not self.dry_run: + ensure_directory(sitepy) + with io.open(sitepy, 'w', encoding='utf-8') as strm: + strm.write(source) + self.byte_compile([sitepy]) + + self.sitepy_installed = True + + def create_home_path(self): + """Create directories under ~.""" + if not self.user: + return + home = convert_path(os.path.expanduser("~")) + for name, path in six.iteritems(self.config_vars): + if path.startswith(home) and not os.path.isdir(path): + self.debug_print("os.makedirs('%s', 0o700)" % path) + os.makedirs(path, 0o700) + + if sys.version[:3] in ('2.3', '2.4', '2.5') or 'real_prefix' in sys.__dict__: + sitedir_name = 'site-packages' + else: + sitedir_name = 'dist-packages' + + INSTALL_SCHEMES = dict( + posix=dict( + install_dir='$base/lib/python$py_version_short/site-packages', + script_dir='$base/bin', + ), + unix_local = dict( + install_dir = '$base/local/lib/python$py_version_short/%s' % sitedir_name, + script_dir = '$base/local/bin', + ), + posix_local = dict( + install_dir = '$base/local/lib/python$py_version_short/%s' % sitedir_name, + script_dir = '$base/local/bin', + ), + deb_system = dict( + install_dir = '$base/lib/python3/%s' % sitedir_name, + script_dir = '$base/bin', + ), + ) + + DEFAULT_SCHEME = dict( + install_dir='$base/Lib/site-packages', + script_dir='$base/Scripts', + ) + + def _expand(self, *attrs): + config_vars = self.get_finalized_command('install').config_vars + + if self.prefix or self.install_layout: + if self.install_layout and self.install_layout in ['deb']: + scheme_name = "deb_system" + self.prefix = '/usr' + elif self.prefix or 'real_prefix' in sys.__dict__: + scheme_name = os.name + else: + scheme_name = "posix_local" + # Set default install_dir/scripts from --prefix + config_vars = config_vars.copy() + config_vars['base'] = self.prefix + scheme = self.INSTALL_SCHEMES.get(scheme_name,self.DEFAULT_SCHEME) + for attr, val in scheme.items(): + if getattr(self, attr, None) is None: + setattr(self, attr, val) + + from distutils.util import subst_vars + + for attr in attrs: + val = getattr(self, attr) + if val is not None: + val = subst_vars(val, config_vars) + if os.name == 'posix': + val = os.path.expanduser(val) + setattr(self, attr, val) + + +def _pythonpath(): + items = os.environ.get('PYTHONPATH', '').split(os.pathsep) + return filter(None, items) + + +def get_site_dirs(): + """ + Return a list of 'site' dirs + """ + + sitedirs = [] + + # start with PYTHONPATH + sitedirs.extend(_pythonpath()) + + prefixes = [sys.prefix] + if sys.exec_prefix != sys.prefix: + prefixes.append(sys.exec_prefix) + for prefix in prefixes: + if prefix: + if sys.platform in ('os2emx', 'riscos'): + sitedirs.append(os.path.join(prefix, "Lib", "site-packages")) + elif os.sep == '/': + sitedirs.extend([ + os.path.join( + prefix, + "local/lib", + "python" + sys.version[:3], + "dist-packages", + ), + os.path.join( + prefix, + "lib", + "python{}.{}".format(*sys.version_info), + "dist-packages", + ), + os.path.join(prefix, "lib", "site-python"), + ]) + else: + sitedirs.extend([ + prefix, + os.path.join(prefix, "lib", "site-packages"), + ]) + if sys.platform == 'darwin': + # for framework builds *only* we add the standard Apple + # locations. Currently only per-user, but /Library and + # /Network/Library could be added too + if 'Python.framework' in prefix: + home = os.environ.get('HOME') + if home: + home_sp = os.path.join( + home, + 'Library', + 'Python', + '{}.{}'.format(*sys.version_info), + 'site-packages', + ) + sitedirs.append(home_sp) + lib_paths = get_path('purelib'), get_path('platlib') + for site_lib in lib_paths: + if site_lib not in sitedirs: + sitedirs.append(site_lib) + + if site.ENABLE_USER_SITE: + sitedirs.append(site.USER_SITE) + + try: + sitedirs.extend(site.getsitepackages()) + except AttributeError: + pass + + sitedirs = list(map(normalize_path, sitedirs)) + + return sitedirs + + +def expand_paths(inputs): + """Yield sys.path directories that might contain "old-style" packages""" + + seen = {} + + for dirname in inputs: + dirname = normalize_path(dirname) + if dirname in seen: + continue + + seen[dirname] = 1 + if not os.path.isdir(dirname): + continue + + files = os.listdir(dirname) + yield dirname, files + + for name in files: + if not name.endswith('.pth'): + # We only care about the .pth files + continue + if name in ('easy-install.pth', 'setuptools.pth'): + # Ignore .pth files that we control + continue + + # Read the .pth file + f = open(os.path.join(dirname, name)) + lines = list(yield_lines(f)) + f.close() + + # Yield existing non-dupe, non-import directory lines from it + for line in lines: + if not line.startswith("import"): + line = normalize_path(line.rstrip()) + if line not in seen: + seen[line] = 1 + if not os.path.isdir(line): + continue + yield line, os.listdir(line) + + +def extract_wininst_cfg(dist_filename): + """Extract configuration data from a bdist_wininst .exe + + Returns a configparser.RawConfigParser, or None + """ + f = open(dist_filename, 'rb') + try: + endrec = zipfile._EndRecData(f) + if endrec is None: + return None + + prepended = (endrec[9] - endrec[5]) - endrec[6] + if prepended < 12: # no wininst data here + return None + f.seek(prepended - 12) + + tag, cfglen, bmlen = struct.unpack("egg path translations for a given .exe file""" + + prefixes = [ + ('PURELIB/', ''), + ('PLATLIB/pywin32_system32', ''), + ('PLATLIB/', ''), + ('SCRIPTS/', 'EGG-INFO/scripts/'), + ('DATA/lib/site-packages', ''), + ] + z = zipfile.ZipFile(exe_filename) + try: + for info in z.infolist(): + name = info.filename + parts = name.split('/') + if len(parts) == 3 and parts[2] == 'PKG-INFO': + if parts[1].endswith('.egg-info'): + prefixes.insert(0, ('/'.join(parts[:2]), 'EGG-INFO/')) + break + if len(parts) != 2 or not name.endswith('.pth'): + continue + if name.endswith('-nspkg.pth'): + continue + if parts[0].upper() in ('PURELIB', 'PLATLIB'): + contents = z.read(name) + if six.PY3: + contents = contents.decode() + for pth in yield_lines(contents): + pth = pth.strip().replace('\\', '/') + if not pth.startswith('import'): + prefixes.append((('%s/%s/' % (parts[0], pth)), '')) + finally: + z.close() + prefixes = [(x.lower(), y) for x, y in prefixes] + prefixes.sort() + prefixes.reverse() + return prefixes + + +class PthDistributions(Environment): + """A .pth file with Distribution paths in it""" + + dirty = False + + def __init__(self, filename, sitedirs=()): + self.filename = filename + self.sitedirs = list(map(normalize_path, sitedirs)) + self.basedir = normalize_path(os.path.dirname(self.filename)) + self._load() + Environment.__init__(self, [], None, None) + for path in yield_lines(self.paths): + list(map(self.add, find_distributions(path, True))) + + def _load(self): + self.paths = [] + saw_import = False + seen = dict.fromkeys(self.sitedirs) + if os.path.isfile(self.filename): + f = open(self.filename, 'rt') + for line in f: + if line.startswith('import'): + saw_import = True + continue + path = line.rstrip() + self.paths.append(path) + if not path.strip() or path.strip().startswith('#'): + continue + # skip non-existent paths, in case somebody deleted a package + # manually, and duplicate paths as well + path = self.paths[-1] = normalize_path( + os.path.join(self.basedir, path) + ) + if not os.path.exists(path) or path in seen: + self.paths.pop() # skip it + self.dirty = True # we cleaned up, so we're dirty now :) + continue + seen[path] = 1 + f.close() + + if self.paths and not saw_import: + self.dirty = True # ensure anything we touch has import wrappers + while self.paths and not self.paths[-1].strip(): + self.paths.pop() + + def save(self): + """Write changed .pth file back to disk""" + if not self.dirty: + return + + rel_paths = list(map(self.make_relative, self.paths)) + if rel_paths: + log.debug("Saving %s", self.filename) + lines = self._wrap_lines(rel_paths) + data = '\n'.join(lines) + '\n' + + if os.path.islink(self.filename): + os.unlink(self.filename) + with open(self.filename, 'wt') as f: + f.write(data) + + elif os.path.exists(self.filename): + log.debug("Deleting empty %s", self.filename) + os.unlink(self.filename) + + self.dirty = False + + @staticmethod + def _wrap_lines(lines): + return lines + + def add(self, dist): + """Add `dist` to the distribution map""" + new_path = ( + dist.location not in self.paths and ( + dist.location not in self.sitedirs or + # account for '.' being in PYTHONPATH + dist.location == os.getcwd() + ) + ) + if new_path: + self.paths.append(dist.location) + self.dirty = True + Environment.add(self, dist) + + def remove(self, dist): + """Remove `dist` from the distribution map""" + while dist.location in self.paths: + self.paths.remove(dist.location) + self.dirty = True + Environment.remove(self, dist) + + def make_relative(self, path): + npath, last = os.path.split(normalize_path(path)) + baselen = len(self.basedir) + parts = [last] + sep = os.altsep == '/' and '/' or os.sep + while len(npath) >= baselen: + if npath == self.basedir: + parts.append(os.curdir) + parts.reverse() + return sep.join(parts) + npath, last = os.path.split(npath) + parts.append(last) + else: + return path + + +class RewritePthDistributions(PthDistributions): + @classmethod + def _wrap_lines(cls, lines): + yield cls.prelude + for line in lines: + yield line + yield cls.postlude + + prelude = _one_liner(""" + import sys + sys.__plen = len(sys.path) + """) + postlude = _one_liner(""" + import sys + new = sys.path[sys.__plen:] + del sys.path[sys.__plen:] + p = getattr(sys, '__egginsert', 0) + sys.path[p:p] = new + sys.__egginsert = p + len(new) + """) + + +if os.environ.get('SETUPTOOLS_SYS_PATH_TECHNIQUE', 'raw') == 'rewrite': + PthDistributions = RewritePthDistributions + + +def _first_line_re(): + """ + Return a regular expression based on first_line_re suitable for matching + strings. + """ + if isinstance(first_line_re.pattern, str): + return first_line_re + + # first_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern. + return re.compile(first_line_re.pattern.decode()) + + +def auto_chmod(func, arg, exc): + if func in [os.unlink, os.remove] and os.name == 'nt': + chmod(arg, stat.S_IWRITE) + return func(arg) + et, ev, _ = sys.exc_info() + six.reraise(et, (ev[0], ev[1] + (" %s %s" % (func, arg)))) + + +def update_dist_caches(dist_path, fix_zipimporter_caches): + """ + Fix any globally cached `dist_path` related data + + `dist_path` should be a path of a newly installed egg distribution (zipped + or unzipped). + + sys.path_importer_cache contains finder objects that have been cached when + importing data from the original distribution. Any such finders need to be + cleared since the replacement distribution might be packaged differently, + e.g. a zipped egg distribution might get replaced with an unzipped egg + folder or vice versa. Having the old finders cached may then cause Python + to attempt loading modules from the replacement distribution using an + incorrect loader. + + zipimport.zipimporter objects are Python loaders charged with importing + data packaged inside zip archives. If stale loaders referencing the + original distribution, are left behind, they can fail to load modules from + the replacement distribution. E.g. if an old zipimport.zipimporter instance + is used to load data from a new zipped egg archive, it may cause the + operation to attempt to locate the requested data in the wrong location - + one indicated by the original distribution's zip archive directory + information. Such an operation may then fail outright, e.g. report having + read a 'bad local file header', or even worse, it may fail silently & + return invalid data. + + zipimport._zip_directory_cache contains cached zip archive directory + information for all existing zipimport.zipimporter instances and all such + instances connected to the same archive share the same cached directory + information. + + If asked, and the underlying Python implementation allows it, we can fix + all existing zipimport.zipimporter instances instead of having to track + them down and remove them one by one, by updating their shared cached zip + archive directory information. This, of course, assumes that the + replacement distribution is packaged as a zipped egg. + + If not asked to fix existing zipimport.zipimporter instances, we still do + our best to clear any remaining zipimport.zipimporter related cached data + that might somehow later get used when attempting to load data from the new + distribution and thus cause such load operations to fail. Note that when + tracking down such remaining stale data, we can not catch every conceivable + usage from here, and we clear only those that we know of and have found to + cause problems if left alive. Any remaining caches should be updated by + whomever is in charge of maintaining them, i.e. they should be ready to + handle us replacing their zip archives with new distributions at runtime. + + """ + # There are several other known sources of stale zipimport.zipimporter + # instances that we do not clear here, but might if ever given a reason to + # do so: + # * Global setuptools pkg_resources.working_set (a.k.a. 'master working + # set') may contain distributions which may in turn contain their + # zipimport.zipimporter loaders. + # * Several zipimport.zipimporter loaders held by local variables further + # up the function call stack when running the setuptools installation. + # * Already loaded modules may have their __loader__ attribute set to the + # exact loader instance used when importing them. Python 3.4 docs state + # that this information is intended mostly for introspection and so is + # not expected to cause us problems. + normalized_path = normalize_path(dist_path) + _uncache(normalized_path, sys.path_importer_cache) + if fix_zipimporter_caches: + _replace_zip_directory_cache_data(normalized_path) + else: + # Here, even though we do not want to fix existing and now stale + # zipimporter cache information, we still want to remove it. Related to + # Python's zip archive directory information cache, we clear each of + # its stale entries in two phases: + # 1. Clear the entry so attempting to access zip archive information + # via any existing stale zipimport.zipimporter instances fails. + # 2. Remove the entry from the cache so any newly constructed + # zipimport.zipimporter instances do not end up using old stale + # zip archive directory information. + # This whole stale data removal step does not seem strictly necessary, + # but has been left in because it was done before we started replacing + # the zip archive directory information cache content if possible, and + # there are no relevant unit tests that we can depend on to tell us if + # this is really needed. + _remove_and_clear_zip_directory_cache_data(normalized_path) + + +def _collect_zipimporter_cache_entries(normalized_path, cache): + """ + Return zipimporter cache entry keys related to a given normalized path. + + Alternative path spellings (e.g. those using different character case or + those using alternative path separators) related to the same path are + included. Any sub-path entries are included as well, i.e. those + corresponding to zip archives embedded in other zip archives. + + """ + result = [] + prefix_len = len(normalized_path) + for p in cache: + np = normalize_path(p) + if (np.startswith(normalized_path) and + np[prefix_len:prefix_len + 1] in (os.sep, '')): + result.append(p) + return result + + +def _update_zipimporter_cache(normalized_path, cache, updater=None): + """ + Update zipimporter cache data for a given normalized path. + + Any sub-path entries are processed as well, i.e. those corresponding to zip + archives embedded in other zip archives. + + Given updater is a callable taking a cache entry key and the original entry + (after already removing the entry from the cache), and expected to update + the entry and possibly return a new one to be inserted in its place. + Returning None indicates that the entry should not be replaced with a new + one. If no updater is given, the cache entries are simply removed without + any additional processing, the same as if the updater simply returned None. + + """ + for p in _collect_zipimporter_cache_entries(normalized_path, cache): + # N.B. pypy's custom zipimport._zip_directory_cache implementation does + # not support the complete dict interface: + # * Does not support item assignment, thus not allowing this function + # to be used only for removing existing cache entries. + # * Does not support the dict.pop() method, forcing us to use the + # get/del patterns instead. For more detailed information see the + # following links: + # https://github.com/pypa/setuptools/issues/202#issuecomment-202913420 + # http://bit.ly/2h9itJX + old_entry = cache[p] + del cache[p] + new_entry = updater and updater(p, old_entry) + if new_entry is not None: + cache[p] = new_entry + + +def _uncache(normalized_path, cache): + _update_zipimporter_cache(normalized_path, cache) + + +def _remove_and_clear_zip_directory_cache_data(normalized_path): + def clear_and_remove_cached_zip_archive_directory_data(path, old_entry): + old_entry.clear() + + _update_zipimporter_cache( + normalized_path, zipimport._zip_directory_cache, + updater=clear_and_remove_cached_zip_archive_directory_data) + + +# PyPy Python implementation does not allow directly writing to the +# zipimport._zip_directory_cache and so prevents us from attempting to correct +# its content. The best we can do there is clear the problematic cache content +# and have PyPy repopulate it as needed. The downside is that if there are any +# stale zipimport.zipimporter instances laying around, attempting to use them +# will fail due to not having its zip archive directory information available +# instead of being automatically corrected to use the new correct zip archive +# directory information. +if '__pypy__' in sys.builtin_module_names: + _replace_zip_directory_cache_data = \ + _remove_and_clear_zip_directory_cache_data +else: + + def _replace_zip_directory_cache_data(normalized_path): + def replace_cached_zip_archive_directory_data(path, old_entry): + # N.B. In theory, we could load the zip directory information just + # once for all updated path spellings, and then copy it locally and + # update its contained path strings to contain the correct + # spelling, but that seems like a way too invasive move (this cache + # structure is not officially documented anywhere and could in + # theory change with new Python releases) for no significant + # benefit. + old_entry.clear() + zipimport.zipimporter(path) + old_entry.update(zipimport._zip_directory_cache[path]) + return old_entry + + _update_zipimporter_cache( + normalized_path, zipimport._zip_directory_cache, + updater=replace_cached_zip_archive_directory_data) + + +def is_python(text, filename=''): + "Is this string a valid Python script?" + try: + compile(text, filename, 'exec') + except (SyntaxError, TypeError): + return False + else: + return True + + +def is_sh(executable): + """Determine if the specified executable is a .sh (contains a #! line)""" + try: + with io.open(executable, encoding='latin-1') as fp: + magic = fp.read(2) + except (OSError, IOError): + return executable + return magic == '#!' + + +def nt_quote_arg(arg): + """Quote a command line argument according to Windows parsing rules""" + return subprocess.list2cmdline([arg]) + + +def is_python_script(script_text, filename): + """Is this text, as a whole, a Python script? (as opposed to shell/bat/etc. + """ + if filename.endswith('.py') or filename.endswith('.pyw'): + return True # extension says it's Python + if is_python(script_text, filename): + return True # it's syntactically valid Python + if script_text.startswith('#!'): + # It begins with a '#!' line, so check if 'python' is in it somewhere + return 'python' in script_text.splitlines()[0].lower() + + return False # Not any Python I can recognize + + +try: + from os import chmod as _chmod +except ImportError: + # Jython compatibility + def _chmod(*args): + pass + + +def chmod(path, mode): + log.debug("changing mode of %s to %o", path, mode) + try: + _chmod(path, mode) + except os.error as e: + log.debug("chmod failed: %s", e) + + +class CommandSpec(list): + """ + A command spec for a #! header, specified as a list of arguments akin to + those passed to Popen. + """ + + options = [] + split_args = dict() + + @classmethod + def best(cls): + """ + Choose the best CommandSpec class based on environmental conditions. + """ + return cls + + @classmethod + def _sys_executable(cls): + _default = os.path.normpath(sys.executable) + return os.environ.get('__PYVENV_LAUNCHER__', _default) + + @classmethod + def from_param(cls, param): + """ + Construct a CommandSpec from a parameter to build_scripts, which may + be None. + """ + if isinstance(param, cls): + return param + if isinstance(param, list): + return cls(param) + if param is None: + return cls.from_environment() + # otherwise, assume it's a string. + return cls.from_string(param) + + @classmethod + def from_environment(cls): + return cls([cls._sys_executable()]) + + @classmethod + def from_string(cls, string): + """ + Construct a command spec from a simple string representing a command + line parseable by shlex.split. + """ + items = shlex.split(string, **cls.split_args) + return cls(items) + + def install_options(self, script_text): + self.options = shlex.split(self._extract_options(script_text)) + cmdline = subprocess.list2cmdline(self) + if not isascii(cmdline): + self.options[:0] = ['-x'] + + @staticmethod + def _extract_options(orig_script): + """ + Extract any options from the first line of the script. + """ + first = (orig_script + '\n').splitlines()[0] + match = _first_line_re().match(first) + options = match.group(1) or '' if match else '' + return options.strip() + + def as_header(self): + return self._render(self + list(self.options)) + + @staticmethod + def _strip_quotes(item): + _QUOTES = '"\'' + for q in _QUOTES: + if item.startswith(q) and item.endswith(q): + return item[1:-1] + return item + + @staticmethod + def _render(items): + cmdline = subprocess.list2cmdline( + CommandSpec._strip_quotes(item.strip()) for item in items) + return '#!' + cmdline + '\n' + + +# For pbr compat; will be removed in a future version. +sys_executable = CommandSpec._sys_executable() + + +class WindowsCommandSpec(CommandSpec): + split_args = dict(posix=False) + + +class ScriptWriter: + """ + Encapsulates behavior around writing entry point scripts for console and + gui apps. + """ + + template = textwrap.dedent(r""" + # EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r + __requires__ = %(spec)r + import re + import sys + from pkg_resources import load_entry_point + + if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit( + load_entry_point(%(spec)r, %(group)r, %(name)r)() + ) + """).lstrip() + + command_spec_class = CommandSpec + + @classmethod + def get_script_args(cls, dist, executable=None, wininst=False): + # for backward compatibility + warnings.warn("Use get_args", EasyInstallDeprecationWarning) + writer = (WindowsScriptWriter if wininst else ScriptWriter).best() + header = cls.get_script_header("", executable, wininst) + return writer.get_args(dist, header) + + @classmethod + def get_script_header(cls, script_text, executable=None, wininst=False): + # for backward compatibility + warnings.warn("Use get_header", EasyInstallDeprecationWarning, stacklevel=2) + if wininst: + executable = "python.exe" + return cls.get_header(script_text, executable) + + @classmethod + def get_args(cls, dist, header=None): + """ + Yield write_script() argument tuples for a distribution's + console_scripts and gui_scripts entry points. + """ + if header is None: + header = cls.get_header() + spec = str(dist.as_requirement()) + for type_ in 'console', 'gui': + group = type_ + '_scripts' + for name, ep in dist.get_entry_map(group).items(): + cls._ensure_safe_name(name) + script_text = cls.template % locals() + args = cls._get_script_args(type_, name, header, script_text) + for res in args: + yield res + + @staticmethod + def _ensure_safe_name(name): + """ + Prevent paths in *_scripts entry point names. + """ + has_path_sep = re.search(r'[\\/]', name) + if has_path_sep: + raise ValueError("Path separators not allowed in script names") + + @classmethod + def get_writer(cls, force_windows): + # for backward compatibility + warnings.warn("Use best", EasyInstallDeprecationWarning) + return WindowsScriptWriter.best() if force_windows else cls.best() + + @classmethod + def best(cls): + """ + Select the best ScriptWriter for this environment. + """ + if sys.platform == 'win32' or (os.name == 'java' and os._name == 'nt'): + return WindowsScriptWriter.best() + else: + return cls + + @classmethod + def _get_script_args(cls, type_, name, header, script_text): + # Simply write the stub with no extension. + yield (name, header + script_text) + + @classmethod + def get_header(cls, script_text="", executable=None): + """Create a #! line, getting options (if any) from script_text""" + cmd = cls.command_spec_class.best().from_param(executable) + cmd.install_options(script_text) + return cmd.as_header() + + +class WindowsScriptWriter(ScriptWriter): + command_spec_class = WindowsCommandSpec + + @classmethod + def get_writer(cls): + # for backward compatibility + warnings.warn("Use best", EasyInstallDeprecationWarning) + return cls.best() + + @classmethod + def best(cls): + """ + Select the best ScriptWriter suitable for Windows + """ + writer_lookup = dict( + executable=WindowsExecutableLauncherWriter, + natural=cls, + ) + # for compatibility, use the executable launcher by default + launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable') + return writer_lookup[launcher] + + @classmethod + def _get_script_args(cls, type_, name, header, script_text): + "For Windows, add a .py extension" + ext = dict(console='.pya', gui='.pyw')[type_] + if ext not in os.environ['PATHEXT'].lower().split(';'): + msg = ( + "{ext} not listed in PATHEXT; scripts will not be " + "recognized as executables." + ).format(**locals()) + warnings.warn(msg, UserWarning) + old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe'] + old.remove(ext) + header = cls._adjust_header(type_, header) + blockers = [name + x for x in old] + yield name + ext, header + script_text, 't', blockers + + @classmethod + def _adjust_header(cls, type_, orig_header): + """ + Make sure 'pythonw' is used for gui and and 'python' is used for + console (regardless of what sys.executable is). + """ + pattern = 'pythonw.exe' + repl = 'python.exe' + if type_ == 'gui': + pattern, repl = repl, pattern + pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE) + new_header = pattern_ob.sub(string=orig_header, repl=repl) + return new_header if cls._use_header(new_header) else orig_header + + @staticmethod + def _use_header(new_header): + """ + Should _adjust_header use the replaced header? + + On non-windows systems, always use. On + Windows systems, only use the replaced header if it resolves + to an executable on the system. + """ + clean_header = new_header[2:-1].strip('"') + return sys.platform != 'win32' or find_executable(clean_header) + + +class WindowsExecutableLauncherWriter(WindowsScriptWriter): + @classmethod + def _get_script_args(cls, type_, name, header, script_text): + """ + For Windows, add a .py extension and an .exe launcher + """ + if type_ == 'gui': + launcher_type = 'gui' + ext = '-script.pyw' + old = ['.pyw'] + else: + launcher_type = 'cli' + ext = '-script.py' + old = ['.py', '.pyc', '.pyo'] + hdr = cls._adjust_header(type_, header) + blockers = [name + x for x in old] + yield (name + ext, hdr + script_text, 't', blockers) + yield ( + name + '.exe', get_win_launcher(launcher_type), + 'b' # write in binary mode + ) + if not is_64bit(): + # install a manifest for the launcher to prevent Windows + # from detecting it as an installer (which it will for + # launchers like easy_install.exe). Consider only + # adding a manifest for launchers detected as installers. + # See Distribute #143 for details. + m_name = name + '.exe.manifest' + yield (m_name, load_launcher_manifest(name), 't') + + +# for backward-compatibility +get_script_args = ScriptWriter.get_script_args +get_script_header = ScriptWriter.get_script_header + + +def get_win_launcher(type): + """ + Load the Windows launcher (executable) suitable for launching a script. + + `type` should be either 'cli' or 'gui' + + Returns the executable as a byte string. + """ + launcher_fn = '%s.exe' % type + if is_64bit(): + launcher_fn = launcher_fn.replace(".", "-64.") + else: + launcher_fn = launcher_fn.replace(".", "-32.") + return resource_string('setuptools', launcher_fn) + + +def load_launcher_manifest(name): + manifest = pkg_resources.resource_string(__name__, 'launcher manifest.xml') + if six.PY2: + return manifest % vars() + else: + return manifest.decode('utf-8') % vars() + + +def rmtree(path, ignore_errors=False, onerror=auto_chmod): + return shutil.rmtree(path, ignore_errors, onerror) + + +def current_umask(): + tmp = os.umask(0o022) + os.umask(tmp) + return tmp + + +def bootstrap(): + # This function is called when setuptools*.egg is run using /bin/sh + import setuptools + + argv0 = os.path.dirname(setuptools.__path__[0]) + sys.argv[0] = argv0 + sys.argv.append(argv0) + main() + + +def main(argv=None, **kw): + from setuptools import setup + from setuptools.dist import Distribution + + class DistributionWithoutHelpCommands(Distribution): + common_usage = "" + + def _show_help(self, *args, **kw): + with _patch_usage(): + Distribution._show_help(self, *args, **kw) + + if argv is None: + argv = sys.argv[1:] + + with _patch_usage(): + setup( + script_args=['-q', 'easy_install', '-v'] + argv, + script_name=sys.argv[0] or 'easy_install', + distclass=DistributionWithoutHelpCommands, + **kw + ) + + +@contextlib.contextmanager +def _patch_usage(): + import distutils.core + USAGE = textwrap.dedent(""" + usage: %(script)s [options] requirement_or_url ... + or: %(script)s --help + """).lstrip() + + def gen_usage(script_name): + return USAGE % dict( + script=os.path.basename(script_name), + ) + + saved = distutils.core.gen_usage + distutils.core.gen_usage = gen_usage + try: + yield + finally: + distutils.core.gen_usage = saved + +class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning): + """Class for warning about deprecations in EasyInstall in SetupTools. Not ignored by default, unlike DeprecationWarning.""" + diff --git a/robot/lib/python3.8/site-packages/setuptools/command/egg_info.py b/robot/lib/python3.8/site-packages/setuptools/command/egg_info.py new file mode 100644 index 0000000000000000000000000000000000000000..b767ef31d3155dd0292f748f8749c405fd1d3258 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/egg_info.py @@ -0,0 +1,717 @@ +"""setuptools.command.egg_info + +Create a distribution's .egg-info directory and contents""" + +from distutils.filelist import FileList as _FileList +from distutils.errors import DistutilsInternalError +from distutils.util import convert_path +from distutils import log +import distutils.errors +import distutils.filelist +import os +import re +import sys +import io +import warnings +import time +import collections + +from setuptools.extern import six +from setuptools.extern.six.moves import map + +from setuptools import Command +from setuptools.command.sdist import sdist +from setuptools.command.sdist import walk_revctrl +from setuptools.command.setopt import edit_config +from setuptools.command import bdist_egg +from pkg_resources import ( + parse_requirements, safe_name, parse_version, + safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename) +import setuptools.unicode_utils as unicode_utils +from setuptools.glob import glob + +from setuptools.extern import packaging +from setuptools import SetuptoolsDeprecationWarning + +def translate_pattern(glob): + """ + Translate a file path glob like '*.txt' in to a regular expression. + This differs from fnmatch.translate which allows wildcards to match + directory separators. It also knows about '**/' which matches any number of + directories. + """ + pat = '' + + # This will split on '/' within [character classes]. This is deliberate. + chunks = glob.split(os.path.sep) + + sep = re.escape(os.sep) + valid_char = '[^%s]' % (sep,) + + for c, chunk in enumerate(chunks): + last_chunk = c == len(chunks) - 1 + + # Chunks that are a literal ** are globstars. They match anything. + if chunk == '**': + if last_chunk: + # Match anything if this is the last component + pat += '.*' + else: + # Match '(name/)*' + pat += '(?:%s+%s)*' % (valid_char, sep) + continue # Break here as the whole path component has been handled + + # Find any special characters in the remainder + i = 0 + chunk_len = len(chunk) + while i < chunk_len: + char = chunk[i] + if char == '*': + # Match any number of name characters + pat += valid_char + '*' + elif char == '?': + # Match a name character + pat += valid_char + elif char == '[': + # Character class + inner_i = i + 1 + # Skip initial !/] chars + if inner_i < chunk_len and chunk[inner_i] == '!': + inner_i = inner_i + 1 + if inner_i < chunk_len and chunk[inner_i] == ']': + inner_i = inner_i + 1 + + # Loop till the closing ] is found + while inner_i < chunk_len and chunk[inner_i] != ']': + inner_i = inner_i + 1 + + if inner_i >= chunk_len: + # Got to the end of the string without finding a closing ] + # Do not treat this as a matching group, but as a literal [ + pat += re.escape(char) + else: + # Grab the insides of the [brackets] + inner = chunk[i + 1:inner_i] + char_class = '' + + # Class negation + if inner[0] == '!': + char_class = '^' + inner = inner[1:] + + char_class += re.escape(inner) + pat += '[%s]' % (char_class,) + + # Skip to the end ] + i = inner_i + else: + pat += re.escape(char) + i += 1 + + # Join each chunk with the dir separator + if not last_chunk: + pat += sep + + pat += r'\Z' + return re.compile(pat, flags=re.MULTILINE|re.DOTALL) + + +class InfoCommon: + tag_build = None + tag_date = None + + @property + def name(self): + return safe_name(self.distribution.get_name()) + + def tagged_version(self): + version = self.distribution.get_version() + # egg_info may be called more than once for a distribution, + # in which case the version string already contains all tags. + if self.vtags and version.endswith(self.vtags): + return safe_version(version) + return safe_version(version + self.vtags) + + def tags(self): + version = '' + if self.tag_build: + version += self.tag_build + if self.tag_date: + version += time.strftime("-%Y%m%d") + return version + vtags = property(tags) + + +class egg_info(InfoCommon, Command): + description = "create a distribution's .egg-info directory" + + user_options = [ + ('egg-base=', 'e', "directory containing .egg-info directories" + " (default: top of the source tree)"), + ('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"), + ('tag-build=', 'b', "Specify explicit tag to add to version number"), + ('no-date', 'D', "Don't include date stamp [default]"), + ] + + boolean_options = ['tag-date'] + negative_opt = { + 'no-date': 'tag-date', + } + + def initialize_options(self): + self.egg_base = None + self.egg_name = None + self.egg_info = None + self.egg_version = None + self.broken_egg_info = False + + #################################### + # allow the 'tag_svn_revision' to be detected and + # set, supporting sdists built on older Setuptools. + @property + def tag_svn_revision(self): + pass + + @tag_svn_revision.setter + def tag_svn_revision(self, value): + pass + #################################### + + def save_version_info(self, filename): + """ + Materialize the value of date into the + build tag. Install build keys in a deterministic order + to avoid arbitrary reordering on subsequent builds. + """ + egg_info = collections.OrderedDict() + # follow the order these keys would have been added + # when PYTHONHASHSEED=0 + egg_info['tag_build'] = self.tags() + egg_info['tag_date'] = 0 + edit_config(filename, dict(egg_info=egg_info)) + + def finalize_options(self): + # Note: we need to capture the current value returned + # by `self.tagged_version()`, so we can later update + # `self.distribution.metadata.version` without + # repercussions. + self.egg_name = self.name + self.egg_version = self.tagged_version() + parsed_version = parse_version(self.egg_version) + + try: + is_version = isinstance(parsed_version, packaging.version.Version) + spec = ( + "%s==%s" if is_version else "%s===%s" + ) + list( + parse_requirements(spec % (self.egg_name, self.egg_version)) + ) + except ValueError: + raise distutils.errors.DistutilsOptionError( + "Invalid distribution name or version syntax: %s-%s" % + (self.egg_name, self.egg_version) + ) + + if self.egg_base is None: + dirs = self.distribution.package_dir + self.egg_base = (dirs or {}).get('', os.curdir) + + self.ensure_dirname('egg_base') + self.egg_info = to_filename(self.egg_name) + '.egg-info' + if self.egg_base != os.curdir: + self.egg_info = os.path.join(self.egg_base, self.egg_info) + if '-' in self.egg_name: + self.check_broken_egg_info() + + # Set package version for the benefit of dumber commands + # (e.g. sdist, bdist_wininst, etc.) + # + self.distribution.metadata.version = self.egg_version + + # If we bootstrapped around the lack of a PKG-INFO, as might be the + # case in a fresh checkout, make sure that any special tags get added + # to the version info + # + pd = self.distribution._patched_dist + if pd is not None and pd.key == self.egg_name.lower(): + pd._version = self.egg_version + pd._parsed_version = parse_version(self.egg_version) + self.distribution._patched_dist = None + + def write_or_delete_file(self, what, filename, data, force=False): + """Write `data` to `filename` or delete if empty + + If `data` is non-empty, this routine is the same as ``write_file()``. + If `data` is empty but not ``None``, this is the same as calling + ``delete_file(filename)`. If `data` is ``None``, then this is a no-op + unless `filename` exists, in which case a warning is issued about the + orphaned file (if `force` is false), or deleted (if `force` is true). + """ + if data: + self.write_file(what, filename, data) + elif os.path.exists(filename): + if data is None and not force: + log.warn( + "%s not set in setup(), but %s exists", what, filename + ) + return + else: + self.delete_file(filename) + + def write_file(self, what, filename, data): + """Write `data` to `filename` (if not a dry run) after announcing it + + `what` is used in a log message to identify what is being written + to the file. + """ + log.info("writing %s to %s", what, filename) + if six.PY3: + data = data.encode("utf-8") + if not self.dry_run: + f = open(filename, 'wb') + f.write(data) + f.close() + + def delete_file(self, filename): + """Delete `filename` (if not a dry run) after announcing it""" + log.info("deleting %s", filename) + if not self.dry_run: + os.unlink(filename) + + def run(self): + self.mkpath(self.egg_info) + os.utime(self.egg_info, None) + installer = self.distribution.fetch_build_egg + for ep in iter_entry_points('egg_info.writers'): + ep.require(installer=installer) + writer = ep.resolve() + writer(self, ep.name, os.path.join(self.egg_info, ep.name)) + + # Get rid of native_libs.txt if it was put there by older bdist_egg + nl = os.path.join(self.egg_info, "native_libs.txt") + if os.path.exists(nl): + self.delete_file(nl) + + self.find_sources() + + def find_sources(self): + """Generate SOURCES.txt manifest file""" + manifest_filename = os.path.join(self.egg_info, "SOURCES.txt") + mm = manifest_maker(self.distribution) + mm.manifest = manifest_filename + mm.run() + self.filelist = mm.filelist + + def check_broken_egg_info(self): + bei = self.egg_name + '.egg-info' + if self.egg_base != os.curdir: + bei = os.path.join(self.egg_base, bei) + if os.path.exists(bei): + log.warn( + "-" * 78 + '\n' + "Note: Your current .egg-info directory has a '-' in its name;" + '\nthis will not work correctly with "setup.py develop".\n\n' + 'Please rename %s to %s to correct this problem.\n' + '-' * 78, + bei, self.egg_info + ) + self.broken_egg_info = self.egg_info + self.egg_info = bei # make it work for now + + +class FileList(_FileList): + # Implementations of the various MANIFEST.in commands + + def process_template_line(self, line): + # Parse the line: split it up, make sure the right number of words + # is there, and return the relevant words. 'action' is always + # defined: it's the first word of the line. Which of the other + # three are defined depends on the action; it'll be either + # patterns, (dir and patterns), or (dir_pattern). + (action, patterns, dir, dir_pattern) = self._parse_template_line(line) + + # OK, now we know that the action is valid and we have the + # right number of words on the line for that action -- so we + # can proceed with minimal error-checking. + if action == 'include': + self.debug_print("include " + ' '.join(patterns)) + for pattern in patterns: + if not self.include(pattern): + log.warn("warning: no files found matching '%s'", pattern) + + elif action == 'exclude': + self.debug_print("exclude " + ' '.join(patterns)) + for pattern in patterns: + if not self.exclude(pattern): + log.warn(("warning: no previously-included files " + "found matching '%s'"), pattern) + + elif action == 'global-include': + self.debug_print("global-include " + ' '.join(patterns)) + for pattern in patterns: + if not self.global_include(pattern): + log.warn(("warning: no files found matching '%s' " + "anywhere in distribution"), pattern) + + elif action == 'global-exclude': + self.debug_print("global-exclude " + ' '.join(patterns)) + for pattern in patterns: + if not self.global_exclude(pattern): + log.warn(("warning: no previously-included files matching " + "'%s' found anywhere in distribution"), + pattern) + + elif action == 'recursive-include': + self.debug_print("recursive-include %s %s" % + (dir, ' '.join(patterns))) + for pattern in patterns: + if not self.recursive_include(dir, pattern): + log.warn(("warning: no files found matching '%s' " + "under directory '%s'"), + pattern, dir) + + elif action == 'recursive-exclude': + self.debug_print("recursive-exclude %s %s" % + (dir, ' '.join(patterns))) + for pattern in patterns: + if not self.recursive_exclude(dir, pattern): + log.warn(("warning: no previously-included files matching " + "'%s' found under directory '%s'"), + pattern, dir) + + elif action == 'graft': + self.debug_print("graft " + dir_pattern) + if not self.graft(dir_pattern): + log.warn("warning: no directories found matching '%s'", + dir_pattern) + + elif action == 'prune': + self.debug_print("prune " + dir_pattern) + if not self.prune(dir_pattern): + log.warn(("no previously-included directories found " + "matching '%s'"), dir_pattern) + + else: + raise DistutilsInternalError( + "this cannot happen: invalid action '%s'" % action) + + def _remove_files(self, predicate): + """ + Remove all files from the file list that match the predicate. + Return True if any matching files were removed + """ + found = False + for i in range(len(self.files) - 1, -1, -1): + if predicate(self.files[i]): + self.debug_print(" removing " + self.files[i]) + del self.files[i] + found = True + return found + + def include(self, pattern): + """Include files that match 'pattern'.""" + found = [f for f in glob(pattern) if not os.path.isdir(f)] + self.extend(found) + return bool(found) + + def exclude(self, pattern): + """Exclude files that match 'pattern'.""" + match = translate_pattern(pattern) + return self._remove_files(match.match) + + def recursive_include(self, dir, pattern): + """ + Include all files anywhere in 'dir/' that match the pattern. + """ + full_pattern = os.path.join(dir, '**', pattern) + found = [f for f in glob(full_pattern, recursive=True) + if not os.path.isdir(f)] + self.extend(found) + return bool(found) + + def recursive_exclude(self, dir, pattern): + """ + Exclude any file anywhere in 'dir/' that match the pattern. + """ + match = translate_pattern(os.path.join(dir, '**', pattern)) + return self._remove_files(match.match) + + def graft(self, dir): + """Include all files from 'dir/'.""" + found = [ + item + for match_dir in glob(dir) + for item in distutils.filelist.findall(match_dir) + ] + self.extend(found) + return bool(found) + + def prune(self, dir): + """Filter out files from 'dir/'.""" + match = translate_pattern(os.path.join(dir, '**')) + return self._remove_files(match.match) + + def global_include(self, pattern): + """ + Include all files anywhere in the current directory that match the + pattern. This is very inefficient on large file trees. + """ + if self.allfiles is None: + self.findall() + match = translate_pattern(os.path.join('**', pattern)) + found = [f for f in self.allfiles if match.match(f)] + self.extend(found) + return bool(found) + + def global_exclude(self, pattern): + """ + Exclude all files anywhere that match the pattern. + """ + match = translate_pattern(os.path.join('**', pattern)) + return self._remove_files(match.match) + + def append(self, item): + if item.endswith('\r'): # Fix older sdists built on Windows + item = item[:-1] + path = convert_path(item) + + if self._safe_path(path): + self.files.append(path) + + def extend(self, paths): + self.files.extend(filter(self._safe_path, paths)) + + def _repair(self): + """ + Replace self.files with only safe paths + + Because some owners of FileList manipulate the underlying + ``files`` attribute directly, this method must be called to + repair those paths. + """ + self.files = list(filter(self._safe_path, self.files)) + + def _safe_path(self, path): + enc_warn = "'%s' not %s encodable -- skipping" + + # To avoid accidental trans-codings errors, first to unicode + u_path = unicode_utils.filesys_decode(path) + if u_path is None: + log.warn("'%s' in unexpected encoding -- skipping" % path) + return False + + # Must ensure utf-8 encodability + utf8_path = unicode_utils.try_encode(u_path, "utf-8") + if utf8_path is None: + log.warn(enc_warn, path, 'utf-8') + return False + + try: + # accept is either way checks out + if os.path.exists(u_path) or os.path.exists(utf8_path): + return True + # this will catch any encode errors decoding u_path + except UnicodeEncodeError: + log.warn(enc_warn, path, sys.getfilesystemencoding()) + + +class manifest_maker(sdist): + template = "MANIFEST.in" + + def initialize_options(self): + self.use_defaults = 1 + self.prune = 1 + self.manifest_only = 1 + self.force_manifest = 1 + + def finalize_options(self): + pass + + def run(self): + self.filelist = FileList() + if not os.path.exists(self.manifest): + self.write_manifest() # it must exist so it'll get in the list + self.add_defaults() + if os.path.exists(self.template): + self.read_template() + self.prune_file_list() + self.filelist.sort() + self.filelist.remove_duplicates() + self.write_manifest() + + def _manifest_normalize(self, path): + path = unicode_utils.filesys_decode(path) + return path.replace(os.sep, '/') + + def write_manifest(self): + """ + Write the file list in 'self.filelist' to the manifest file + named by 'self.manifest'. + """ + self.filelist._repair() + + # Now _repairs should encodability, but not unicode + files = [self._manifest_normalize(f) for f in self.filelist.files] + msg = "writing manifest file '%s'" % self.manifest + self.execute(write_file, (self.manifest, files), msg) + + def warn(self, msg): + if not self._should_suppress_warning(msg): + sdist.warn(self, msg) + + @staticmethod + def _should_suppress_warning(msg): + """ + suppress missing-file warnings from sdist + """ + return re.match(r"standard file .*not found", msg) + + def add_defaults(self): + sdist.add_defaults(self) + self.check_license() + self.filelist.append(self.template) + self.filelist.append(self.manifest) + rcfiles = list(walk_revctrl()) + if rcfiles: + self.filelist.extend(rcfiles) + elif os.path.exists(self.manifest): + self.read_manifest() + + if os.path.exists("setup.py"): + # setup.py should be included by default, even if it's not + # the script called to create the sdist + self.filelist.append("setup.py") + + ei_cmd = self.get_finalized_command('egg_info') + self.filelist.graft(ei_cmd.egg_info) + + def prune_file_list(self): + build = self.get_finalized_command('build') + base_dir = self.distribution.get_fullname() + self.filelist.prune(build.build_base) + self.filelist.prune(base_dir) + sep = re.escape(os.sep) + self.filelist.exclude_pattern(r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep, + is_regex=1) + + +def write_file(filename, contents): + """Create a file with the specified name and write 'contents' (a + sequence of strings without line terminators) to it. + """ + contents = "\n".join(contents) + + # assuming the contents has been vetted for utf-8 encoding + contents = contents.encode("utf-8") + + with open(filename, "wb") as f: # always write POSIX-style manifest + f.write(contents) + + +def write_pkg_info(cmd, basename, filename): + log.info("writing %s", filename) + if not cmd.dry_run: + metadata = cmd.distribution.metadata + metadata.version, oldver = cmd.egg_version, metadata.version + metadata.name, oldname = cmd.egg_name, metadata.name + + try: + # write unescaped data to PKG-INFO, so older pkg_resources + # can still parse it + metadata.write_pkg_info(cmd.egg_info) + finally: + metadata.name, metadata.version = oldname, oldver + + safe = getattr(cmd.distribution, 'zip_safe', None) + + bdist_egg.write_safety_flag(cmd.egg_info, safe) + + +def warn_depends_obsolete(cmd, basename, filename): + if os.path.exists(filename): + log.warn( + "WARNING: 'depends.txt' is not used by setuptools 0.6!\n" + "Use the install_requires/extras_require setup() args instead." + ) + + +def _write_requirements(stream, reqs): + lines = yield_lines(reqs or ()) + append_cr = lambda line: line + '\n' + lines = map(append_cr, sorted(lines)) + stream.writelines(lines) + + +def write_requirements(cmd, basename, filename): + dist = cmd.distribution + data = six.StringIO() + _write_requirements(data, dist.install_requires) + extras_require = dist.extras_require or {} + for extra in sorted(extras_require): + data.write('\n[{extra}]\n'.format(**vars())) + _write_requirements(data, extras_require[extra]) + cmd.write_or_delete_file("requirements", filename, data.getvalue()) + + +def write_setup_requirements(cmd, basename, filename): + data = io.StringIO() + _write_requirements(data, cmd.distribution.setup_requires) + cmd.write_or_delete_file("setup-requirements", filename, data.getvalue()) + + +def write_toplevel_names(cmd, basename, filename): + pkgs = dict.fromkeys( + [ + k.split('.', 1)[0] + for k in cmd.distribution.iter_distribution_names() + ] + ) + cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n') + + +def overwrite_arg(cmd, basename, filename): + write_arg(cmd, basename, filename, True) + + +def write_arg(cmd, basename, filename, force=False): + argname = os.path.splitext(basename)[0] + value = getattr(cmd.distribution, argname, None) + if value is not None: + value = '\n'.join(value) + '\n' + cmd.write_or_delete_file(argname, filename, value, force) + + +def write_entries(cmd, basename, filename): + ep = cmd.distribution.entry_points + + if isinstance(ep, six.string_types) or ep is None: + data = ep + elif ep is not None: + data = [] + for section, contents in sorted(ep.items()): + if not isinstance(contents, six.string_types): + contents = EntryPoint.parse_group(section, contents) + contents = '\n'.join(sorted(map(str, contents.values()))) + data.append('[%s]\n%s\n\n' % (section, contents)) + data = ''.join(data) + + cmd.write_or_delete_file('entry points', filename, data, True) + + +def get_pkg_info_revision(): + """ + Get a -r### off of PKG-INFO Version in case this is an sdist of + a subversion revision. + """ + warnings.warn("get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning) + if os.path.exists('PKG-INFO'): + with io.open('PKG-INFO') as f: + for line in f: + match = re.match(r"Version:.*-r(\d+)\s*$", line) + if match: + return int(match.group(1)) + return 0 + + +class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning): + """Class for warning about deprecations in eggInfo in setupTools. Not ignored by default, unlike DeprecationWarning.""" diff --git a/robot/lib/python3.8/site-packages/setuptools/command/install.py b/robot/lib/python3.8/site-packages/setuptools/command/install.py new file mode 100644 index 0000000000000000000000000000000000000000..72b9a3e424707633c7e31a347170f358cfa3f87a --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/install.py @@ -0,0 +1,125 @@ +from distutils.errors import DistutilsArgError +import inspect +import glob +import warnings +import platform +import distutils.command.install as orig + +import setuptools + +# Prior to numpy 1.9, NumPy relies on the '_install' name, so provide it for +# now. See https://github.com/pypa/setuptools/issues/199/ +_install = orig.install + + +class install(orig.install): + """Use easy_install to install the package, w/dependencies""" + + user_options = orig.install.user_options + [ + ('old-and-unmanageable', None, "Try not to use this!"), + ('single-version-externally-managed', None, + "used by system package builders to create 'flat' eggs"), + ] + boolean_options = orig.install.boolean_options + [ + 'old-and-unmanageable', 'single-version-externally-managed', + ] + new_commands = [ + ('install_egg_info', lambda self: True), + ('install_scripts', lambda self: True), + ] + _nc = dict(new_commands) + + def initialize_options(self): + orig.install.initialize_options(self) + self.old_and_unmanageable = None + self.single_version_externally_managed = None + + def finalize_options(self): + orig.install.finalize_options(self) + if self.root: + self.single_version_externally_managed = True + elif self.single_version_externally_managed: + if not self.root and not self.record: + raise DistutilsArgError( + "You must specify --record or --root when building system" + " packages" + ) + + def handle_extra_path(self): + if self.root or self.single_version_externally_managed: + # explicit backward-compatibility mode, allow extra_path to work + return orig.install.handle_extra_path(self) + + # Ignore extra_path when installing an egg (or being run by another + # command without --root or --single-version-externally-managed + self.path_file = None + self.extra_dirs = '' + + def run(self): + # Explicit request for old-style install? Just do it + if self.old_and_unmanageable or self.single_version_externally_managed: + return orig.install.run(self) + + if not self._called_from_setup(inspect.currentframe()): + # Run in backward-compatibility mode to support bdist_* commands. + orig.install.run(self) + else: + self.do_egg_install() + + @staticmethod + def _called_from_setup(run_frame): + """ + Attempt to detect whether run() was called from setup() or by another + command. If called by setup(), the parent caller will be the + 'run_command' method in 'distutils.dist', and *its* caller will be + the 'run_commands' method. If called any other way, the + immediate caller *might* be 'run_command', but it won't have been + called by 'run_commands'. Return True in that case or if a call stack + is unavailable. Return False otherwise. + """ + if run_frame is None: + msg = "Call stack not available. bdist_* commands may fail." + warnings.warn(msg) + if platform.python_implementation() == 'IronPython': + msg = "For best results, pass -X:Frames to enable call stack." + warnings.warn(msg) + return True + res = inspect.getouterframes(run_frame)[2] + caller, = res[:1] + info = inspect.getframeinfo(caller) + caller_module = caller.f_globals.get('__name__', '') + return ( + caller_module == 'distutils.dist' + and info.function == 'run_commands' + ) + + def do_egg_install(self): + + easy_install = self.distribution.get_command_class('easy_install') + + cmd = easy_install( + self.distribution, args="x", root=self.root, record=self.record, + ) + cmd.ensure_finalized() # finalize before bdist_egg munges install cmd + cmd.always_copy_from = '.' # make sure local-dir eggs get installed + + # pick up setup-dir .egg files only: no .egg-info + cmd.package_index.scan(glob.glob('*.egg')) + + self.run_command('bdist_egg') + args = [self.distribution.get_command_obj('bdist_egg').egg_output] + + if setuptools.bootstrap_install_from: + # Bootstrap self-installation of setuptools + args.insert(0, setuptools.bootstrap_install_from) + + cmd.args = args + cmd.run(show_deprecation=False) + setuptools.bootstrap_install_from = None + + +# XXX Python 3.1 doesn't see _nc if this is inside the class +install.sub_commands = ( + [cmd for cmd in orig.install.sub_commands if cmd[0] not in install._nc] + + install.new_commands +) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/install_egg_info.py b/robot/lib/python3.8/site-packages/setuptools/command/install_egg_info.py new file mode 100644 index 0000000000000000000000000000000000000000..5f405bcad743bac704e90c5489713a5cd4404497 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/install_egg_info.py @@ -0,0 +1,82 @@ +from distutils import log, dir_util +import os, sys + +from setuptools import Command +from setuptools import namespaces +from setuptools.archive_util import unpack_archive +import pkg_resources + + +class install_egg_info(namespaces.Installer, Command): + """Install an .egg-info directory for the package""" + + description = "Install an .egg-info directory for the package" + + user_options = [ + ('install-dir=', 'd', "directory to install to"), + ] + + def initialize_options(self): + self.install_dir = None + self.install_layout = None + self.prefix_option = None + + def finalize_options(self): + self.set_undefined_options('install_lib', + ('install_dir', 'install_dir')) + self.set_undefined_options('install',('install_layout','install_layout')) + if sys.hexversion > 0x2060000: + self.set_undefined_options('install',('prefix_option','prefix_option')) + ei_cmd = self.get_finalized_command("egg_info") + basename = pkg_resources.Distribution( + None, None, ei_cmd.egg_name, ei_cmd.egg_version + ).egg_name() + '.egg-info' + + if self.install_layout: + if not self.install_layout.lower() in ['deb']: + raise DistutilsOptionError("unknown value for --install-layout") + self.install_layout = self.install_layout.lower() + basename = basename.replace('-py%s' % pkg_resources.PY_MAJOR, '') + elif self.prefix_option or 'real_prefix' in sys.__dict__: + # don't modify for virtualenv + pass + else: + basename = basename.replace('-py%s' % pkg_resources.PY_MAJOR, '') + + self.source = ei_cmd.egg_info + self.target = os.path.join(self.install_dir, basename) + self.outputs = [] + + def run(self): + self.run_command('egg_info') + if os.path.isdir(self.target) and not os.path.islink(self.target): + dir_util.remove_tree(self.target, dry_run=self.dry_run) + elif os.path.exists(self.target): + self.execute(os.unlink, (self.target,), "Removing " + self.target) + if not self.dry_run: + pkg_resources.ensure_directory(self.target) + self.execute( + self.copytree, (), "Copying %s to %s" % (self.source, self.target) + ) + self.install_namespaces() + + def get_outputs(self): + return self.outputs + + def copytree(self): + # Copy the .egg-info tree to site-packages + def skimmer(src, dst): + # filter out source-control directories; note that 'src' is always + # a '/'-separated path, regardless of platform. 'dst' is a + # platform-specific path. + for skip in '.svn/', 'CVS/': + if src.startswith(skip) or '/' + skip in src: + return None + if self.install_layout and self.install_layout in ['deb'] and src.startswith('SOURCES.txt'): + log.info("Skipping SOURCES.txt") + return None + self.outputs.append(dst) + log.debug("Copying %s to %s", src, dst) + return dst + + unpack_archive(self.source, self.target, skimmer) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/install_lib.py b/robot/lib/python3.8/site-packages/setuptools/command/install_lib.py new file mode 100644 index 0000000000000000000000000000000000000000..bf81519d98e8221707f45c1a3901b8d836095d30 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/install_lib.py @@ -0,0 +1,147 @@ +import os +import sys +from itertools import product, starmap +import distutils.command.install_lib as orig + + +class install_lib(orig.install_lib): + """Don't add compiled flags to filenames of non-Python files""" + + def initialize_options(self): + orig.install_lib.initialize_options(self) + self.multiarch = None + self.install_layout = None + + def finalize_options(self): + orig.install_lib.finalize_options(self) + self.set_undefined_options('install',('install_layout','install_layout')) + if self.install_layout == 'deb' and sys.version_info[:2] >= (3, 3): + import sysconfig + self.multiarch = sysconfig.get_config_var('MULTIARCH') + + def run(self): + self.build() + outfiles = self.install() + if outfiles is not None: + # always compile, in case we have any extension stubs to deal with + self.byte_compile(outfiles) + + def get_exclusions(self): + """ + Return a collections.Sized collections.Container of paths to be + excluded for single_version_externally_managed installations. + """ + all_packages = ( + pkg + for ns_pkg in self._get_SVEM_NSPs() + for pkg in self._all_packages(ns_pkg) + ) + + excl_specs = product(all_packages, self._gen_exclusion_paths()) + return set(starmap(self._exclude_pkg_path, excl_specs)) + + def _exclude_pkg_path(self, pkg, exclusion_path): + """ + Given a package name and exclusion path within that package, + compute the full exclusion path. + """ + parts = pkg.split('.') + [exclusion_path] + return os.path.join(self.install_dir, *parts) + + @staticmethod + def _all_packages(pkg_name): + """ + >>> list(install_lib._all_packages('foo.bar.baz')) + ['foo.bar.baz', 'foo.bar', 'foo'] + """ + while pkg_name: + yield pkg_name + pkg_name, sep, child = pkg_name.rpartition('.') + + def _get_SVEM_NSPs(self): + """ + Get namespace packages (list) but only for + single_version_externally_managed installations and empty otherwise. + """ + # TODO: is it necessary to short-circuit here? i.e. what's the cost + # if get_finalized_command is called even when namespace_packages is + # False? + if not self.distribution.namespace_packages: + return [] + + install_cmd = self.get_finalized_command('install') + svem = install_cmd.single_version_externally_managed + + return self.distribution.namespace_packages if svem else [] + + @staticmethod + def _gen_exclusion_paths(): + """ + Generate file paths to be excluded for namespace packages (bytecode + cache files). + """ + # always exclude the package module itself + yield '__init__.py' + + yield '__init__.pyc' + yield '__init__.pyo' + + if not hasattr(sys, 'implementation'): + return + + base = os.path.join('__pycache__', '__init__.' + sys.implementation.cache_tag) + yield base + '.pyc' + yield base + '.pyo' + yield base + '.opt-1.pyc' + yield base + '.opt-2.pyc' + + def copy_tree( + self, infile, outfile, + preserve_mode=1, preserve_times=1, preserve_symlinks=0, level=1 + ): + assert preserve_mode and preserve_times and not preserve_symlinks + exclude = self.get_exclusions() + + if not exclude: + import distutils.dir_util + distutils.dir_util._multiarch = self.multiarch + return orig.install_lib.copy_tree(self, infile, outfile) + + # Exclude namespace package __init__.py* files from the output + + from setuptools.archive_util import unpack_directory + from distutils import log + + outfiles = [] + + if self.multiarch: + import sysconfig + ext_suffix = sysconfig.get_config_var ('EXT_SUFFIX') + if ext_suffix.endswith(self.multiarch + ext_suffix[-3:]): + new_suffix = None + else: + new_suffix = "%s-%s%s" % (ext_suffix[:-3], self.multiarch, ext_suffix[-3:]) + + def pf(src, dst): + if dst in exclude: + log.warn("Skipping installation of %s (namespace package)", + dst) + return False + + if self.multiarch and new_suffix and dst.endswith(ext_suffix) and not dst.endswith(new_suffix): + dst = dst.replace(ext_suffix, new_suffix) + log.info("renaming extension to %s", os.path.basename(dst)) + + log.info("copying %s -> %s", src, os.path.dirname(dst)) + outfiles.append(dst) + return dst + + unpack_directory(infile, outfile, pf) + return outfiles + + def get_outputs(self): + outputs = orig.install_lib.get_outputs(self) + exclude = self.get_exclusions() + if exclude: + return [f for f in outputs if f not in exclude] + return outputs diff --git a/robot/lib/python3.8/site-packages/setuptools/command/install_scripts.py b/robot/lib/python3.8/site-packages/setuptools/command/install_scripts.py new file mode 100644 index 0000000000000000000000000000000000000000..16234273a2d36b0b3d821a7a97bf8f03cf3f2948 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/install_scripts.py @@ -0,0 +1,65 @@ +from distutils import log +import distutils.command.install_scripts as orig +import os +import sys + +from pkg_resources import Distribution, PathMetadata, ensure_directory + + +class install_scripts(orig.install_scripts): + """Do normal script install, plus any egg_info wrapper scripts""" + + def initialize_options(self): + orig.install_scripts.initialize_options(self) + self.no_ep = False + + def run(self): + import setuptools.command.easy_install as ei + + self.run_command("egg_info") + if self.distribution.scripts: + orig.install_scripts.run(self) # run first to set up self.outfiles + else: + self.outfiles = [] + if self.no_ep: + # don't install entry point scripts into .egg file! + return + + ei_cmd = self.get_finalized_command("egg_info") + dist = Distribution( + ei_cmd.egg_base, PathMetadata(ei_cmd.egg_base, ei_cmd.egg_info), + ei_cmd.egg_name, ei_cmd.egg_version, + ) + bs_cmd = self.get_finalized_command('build_scripts') + exec_param = getattr(bs_cmd, 'executable', None) + bw_cmd = self.get_finalized_command("bdist_wininst") + is_wininst = getattr(bw_cmd, '_is_running', False) + writer = ei.ScriptWriter + if is_wininst: + exec_param = "python.exe" + writer = ei.WindowsScriptWriter + if exec_param == sys.executable: + # In case the path to the Python executable contains a space, wrap + # it so it's not split up. + exec_param = [exec_param] + # resolve the writer to the environment + writer = writer.best() + cmd = writer.command_spec_class.best().from_param(exec_param) + for args in writer.get_args(dist, cmd.as_header()): + self.write_script(*args) + + def write_script(self, script_name, contents, mode="t", *ignored): + """Write an executable file to the scripts directory""" + from setuptools.command.easy_install import chmod, current_umask + + log.info("Installing %s script to %s", script_name, self.install_dir) + target = os.path.join(self.install_dir, script_name) + self.outfiles.append(target) + + mask = current_umask() + if not self.dry_run: + ensure_directory(target) + f = open(target, "w" + mode) + f.write(contents) + f.close() + chmod(target, 0o777 - mask) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/launcher manifest.xml b/robot/lib/python3.8/site-packages/setuptools/command/launcher manifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..5972a96d8ded85cc14147ffc1400ec67c3b5a578 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/launcher manifest.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/robot/lib/python3.8/site-packages/setuptools/command/py36compat.py b/robot/lib/python3.8/site-packages/setuptools/command/py36compat.py new file mode 100644 index 0000000000000000000000000000000000000000..61063e7542586c05c3af21d31cd917ebd1118272 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/py36compat.py @@ -0,0 +1,136 @@ +import os +from glob import glob +from distutils.util import convert_path +from distutils.command import sdist + +from setuptools.extern.six.moves import filter + + +class sdist_add_defaults: + """ + Mix-in providing forward-compatibility for functionality as found in + distutils on Python 3.7. + + Do not edit the code in this class except to update functionality + as implemented in distutils. Instead, override in the subclass. + """ + + def add_defaults(self): + """Add all the default files to self.filelist: + - README or README.txt + - setup.py + - test/test*.py + - all pure Python modules mentioned in setup script + - all files pointed by package_data (build_py) + - all files defined in data_files. + - all files defined as scripts. + - all C sources listed as part of extensions or C libraries + in the setup script (doesn't catch C headers!) + Warns if (README or README.txt) or setup.py are missing; everything + else is optional. + """ + self._add_defaults_standards() + self._add_defaults_optional() + self._add_defaults_python() + self._add_defaults_data_files() + self._add_defaults_ext() + self._add_defaults_c_libs() + self._add_defaults_scripts() + + @staticmethod + def _cs_path_exists(fspath): + """ + Case-sensitive path existence check + + >>> sdist_add_defaults._cs_path_exists(__file__) + True + >>> sdist_add_defaults._cs_path_exists(__file__.upper()) + False + """ + if not os.path.exists(fspath): + return False + # make absolute so we always have a directory + abspath = os.path.abspath(fspath) + directory, filename = os.path.split(abspath) + return filename in os.listdir(directory) + + def _add_defaults_standards(self): + standards = [self.READMES, self.distribution.script_name] + for fn in standards: + if isinstance(fn, tuple): + alts = fn + got_it = False + for fn in alts: + if self._cs_path_exists(fn): + got_it = True + self.filelist.append(fn) + break + + if not got_it: + self.warn("standard file not found: should have one of " + + ', '.join(alts)) + else: + if self._cs_path_exists(fn): + self.filelist.append(fn) + else: + self.warn("standard file '%s' not found" % fn) + + def _add_defaults_optional(self): + optional = ['test/test*.py', 'setup.cfg'] + for pattern in optional: + files = filter(os.path.isfile, glob(pattern)) + self.filelist.extend(files) + + def _add_defaults_python(self): + # build_py is used to get: + # - python modules + # - files defined in package_data + build_py = self.get_finalized_command('build_py') + + # getting python files + if self.distribution.has_pure_modules(): + self.filelist.extend(build_py.get_source_files()) + + # getting package_data files + # (computed in build_py.data_files by build_py.finalize_options) + for pkg, src_dir, build_dir, filenames in build_py.data_files: + for filename in filenames: + self.filelist.append(os.path.join(src_dir, filename)) + + def _add_defaults_data_files(self): + # getting distribution.data_files + if self.distribution.has_data_files(): + for item in self.distribution.data_files: + if isinstance(item, str): + # plain file + item = convert_path(item) + if os.path.isfile(item): + self.filelist.append(item) + else: + # a (dirname, filenames) tuple + dirname, filenames = item + for f in filenames: + f = convert_path(f) + if os.path.isfile(f): + self.filelist.append(f) + + def _add_defaults_ext(self): + if self.distribution.has_ext_modules(): + build_ext = self.get_finalized_command('build_ext') + self.filelist.extend(build_ext.get_source_files()) + + def _add_defaults_c_libs(self): + if self.distribution.has_c_libraries(): + build_clib = self.get_finalized_command('build_clib') + self.filelist.extend(build_clib.get_source_files()) + + def _add_defaults_scripts(self): + if self.distribution.has_scripts(): + build_scripts = self.get_finalized_command('build_scripts') + self.filelist.extend(build_scripts.get_source_files()) + + +if hasattr(sdist.sdist, '_add_defaults_standards'): + # disable the functionality already available upstream + class sdist_add_defaults: + pass diff --git a/robot/lib/python3.8/site-packages/setuptools/command/register.py b/robot/lib/python3.8/site-packages/setuptools/command/register.py new file mode 100644 index 0000000000000000000000000000000000000000..b8266b9a60f8c363ba35f7b73befd7c9c7cb4abc --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/register.py @@ -0,0 +1,18 @@ +from distutils import log +import distutils.command.register as orig + +from setuptools.errors import RemovedCommandError + + +class register(orig.register): + """Formerly used to register packages on PyPI.""" + + def run(self): + msg = ( + "The register command has been removed, use twine to upload " + + "instead (https://pypi.org/p/twine)" + ) + + self.announce("ERROR: " + msg, log.ERROR) + + raise RemovedCommandError(msg) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/rotate.py b/robot/lib/python3.8/site-packages/setuptools/command/rotate.py new file mode 100644 index 0000000000000000000000000000000000000000..b89353f529b3d08e768dea69a9dc8b5e7403003d --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/rotate.py @@ -0,0 +1,66 @@ +from distutils.util import convert_path +from distutils import log +from distutils.errors import DistutilsOptionError +import os +import shutil + +from setuptools.extern import six + +from setuptools import Command + + +class rotate(Command): + """Delete older distributions""" + + description = "delete older distributions, keeping N newest files" + user_options = [ + ('match=', 'm', "patterns to match (required)"), + ('dist-dir=', 'd', "directory where the distributions are"), + ('keep=', 'k', "number of matching distributions to keep"), + ] + + boolean_options = [] + + def initialize_options(self): + self.match = None + self.dist_dir = None + self.keep = None + + def finalize_options(self): + if self.match is None: + raise DistutilsOptionError( + "Must specify one or more (comma-separated) match patterns " + "(e.g. '.zip' or '.egg')" + ) + if self.keep is None: + raise DistutilsOptionError("Must specify number of files to keep") + try: + self.keep = int(self.keep) + except ValueError: + raise DistutilsOptionError("--keep must be an integer") + if isinstance(self.match, six.string_types): + self.match = [ + convert_path(p.strip()) for p in self.match.split(',') + ] + self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) + + def run(self): + self.run_command("egg_info") + from glob import glob + + for pattern in self.match: + pattern = self.distribution.get_name() + '*' + pattern + files = glob(os.path.join(self.dist_dir, pattern)) + files = [(os.path.getmtime(f), f) for f in files] + files.sort() + files.reverse() + + log.info("%d file(s) matching %s", len(files), pattern) + files = files[self.keep:] + for (t, f) in files: + log.info("Deleting %s", f) + if not self.dry_run: + if os.path.isdir(f): + shutil.rmtree(f) + else: + os.unlink(f) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/saveopts.py b/robot/lib/python3.8/site-packages/setuptools/command/saveopts.py new file mode 100644 index 0000000000000000000000000000000000000000..611cec552867a6d50b7edd700c86c7396d906ea2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/saveopts.py @@ -0,0 +1,22 @@ +from setuptools.command.setopt import edit_config, option_base + + +class saveopts(option_base): + """Save command-line options to a file""" + + description = "save supplied options to setup.cfg or other config file" + + def run(self): + dist = self.distribution + settings = {} + + for cmd in dist.command_options: + + if cmd == 'saveopts': + continue # don't save our own options! + + for opt, (src, val) in dist.get_option_dict(cmd).items(): + if src == "command line": + settings.setdefault(cmd, {})[opt] = val + + edit_config(self.filename, settings, self.dry_run) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/sdist.py b/robot/lib/python3.8/site-packages/setuptools/command/sdist.py new file mode 100644 index 0000000000000000000000000000000000000000..a851453f9aa9506d307e1aa7e802fdee9e943eae --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/sdist.py @@ -0,0 +1,252 @@ +from distutils import log +import distutils.command.sdist as orig +import os +import sys +import io +import contextlib + +from setuptools.extern import six, ordered_set + +from .py36compat import sdist_add_defaults + +import pkg_resources + +_default_revctrl = list + + +def walk_revctrl(dirname=''): + """Find all files under revision control""" + for ep in pkg_resources.iter_entry_points('setuptools.file_finders'): + for item in ep.load()(dirname): + yield item + + +class sdist(sdist_add_defaults, orig.sdist): + """Smart sdist that finds anything supported by revision control""" + + user_options = [ + ('formats=', None, + "formats for source distribution (comma-separated list)"), + ('keep-temp', 'k', + "keep the distribution tree around after creating " + + "archive file(s)"), + ('dist-dir=', 'd', + "directory to put the source distribution archive(s) in " + "[default: dist]"), + ] + + negative_opt = {} + + README_EXTENSIONS = ['', '.rst', '.txt', '.md'] + READMES = tuple('README{0}'.format(ext) for ext in README_EXTENSIONS) + + def run(self): + self.run_command('egg_info') + ei_cmd = self.get_finalized_command('egg_info') + self.filelist = ei_cmd.filelist + self.filelist.append(os.path.join(ei_cmd.egg_info, 'SOURCES.txt')) + self.check_readme() + + # Run sub commands + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + self.make_distribution() + + dist_files = getattr(self.distribution, 'dist_files', []) + for file in self.archive_files: + data = ('sdist', '', file) + if data not in dist_files: + dist_files.append(data) + + def initialize_options(self): + orig.sdist.initialize_options(self) + + self._default_to_gztar() + + def _default_to_gztar(self): + # only needed on Python prior to 3.6. + if sys.version_info >= (3, 6, 0, 'beta', 1): + return + self.formats = ['gztar'] + + def make_distribution(self): + """ + Workaround for #516 + """ + with self._remove_os_link(): + orig.sdist.make_distribution(self) + + @staticmethod + @contextlib.contextmanager + def _remove_os_link(): + """ + In a context, remove and restore os.link if it exists + """ + + class NoValue: + pass + + orig_val = getattr(os, 'link', NoValue) + try: + del os.link + except Exception: + pass + try: + yield + finally: + if orig_val is not NoValue: + setattr(os, 'link', orig_val) + + def __read_template_hack(self): + # This grody hack closes the template file (MANIFEST.in) if an + # exception occurs during read_template. + # Doing so prevents an error when easy_install attempts to delete the + # file. + try: + orig.sdist.read_template(self) + except Exception: + _, _, tb = sys.exc_info() + tb.tb_next.tb_frame.f_locals['template'].close() + raise + + # Beginning with Python 2.7.2, 3.1.4, and 3.2.1, this leaky file handle + # has been fixed, so only override the method if we're using an earlier + # Python. + has_leaky_handle = ( + sys.version_info < (2, 7, 2) + or (3, 0) <= sys.version_info < (3, 1, 4) + or (3, 2) <= sys.version_info < (3, 2, 1) + ) + if has_leaky_handle: + read_template = __read_template_hack + + def _add_defaults_optional(self): + if six.PY2: + sdist_add_defaults._add_defaults_optional(self) + else: + super()._add_defaults_optional() + if os.path.isfile('pyproject.toml'): + self.filelist.append('pyproject.toml') + + def _add_defaults_python(self): + """getting python files""" + if self.distribution.has_pure_modules(): + build_py = self.get_finalized_command('build_py') + self.filelist.extend(build_py.get_source_files()) + self._add_data_files(self._safe_data_files(build_py)) + + def _safe_data_files(self, build_py): + """ + Extracting data_files from build_py is known to cause + infinite recursion errors when `include_package_data` + is enabled, so suppress it in that case. + """ + if self.distribution.include_package_data: + return () + return build_py.data_files + + def _add_data_files(self, data_files): + """ + Add data files as found in build_py.data_files. + """ + self.filelist.extend( + os.path.join(src_dir, name) + for _, src_dir, _, filenames in data_files + for name in filenames + ) + + def _add_defaults_data_files(self): + try: + if six.PY2: + sdist_add_defaults._add_defaults_data_files(self) + else: + super()._add_defaults_data_files() + except TypeError: + log.warn("data_files contains unexpected objects") + + def check_readme(self): + for f in self.READMES: + if os.path.exists(f): + return + else: + self.warn( + "standard file not found: should have one of " + + ', '.join(self.READMES) + ) + + def make_release_tree(self, base_dir, files): + orig.sdist.make_release_tree(self, base_dir, files) + + # Save any egg_info command line options used to create this sdist + dest = os.path.join(base_dir, 'setup.cfg') + if hasattr(os, 'link') and os.path.exists(dest): + # unlink and re-copy, since it might be hard-linked, and + # we don't want to change the source version + os.unlink(dest) + self.copy_file('setup.cfg', dest) + + self.get_finalized_command('egg_info').save_version_info(dest) + + def _manifest_is_not_generated(self): + # check for special comment used in 2.7.1 and higher + if not os.path.isfile(self.manifest): + return False + + with io.open(self.manifest, 'rb') as fp: + first_line = fp.readline() + return (first_line != + '# file GENERATED by distutils, do NOT edit\n'.encode()) + + def read_manifest(self): + """Read the manifest file (named by 'self.manifest') and use it to + fill in 'self.filelist', the list of files to include in the source + distribution. + """ + log.info("reading manifest file '%s'", self.manifest) + manifest = open(self.manifest, 'rb') + for line in manifest: + # The manifest must contain UTF-8. See #303. + if six.PY3: + try: + line = line.decode('UTF-8') + except UnicodeDecodeError: + log.warn("%r not UTF-8 decodable -- skipping" % line) + continue + # ignore comments and blank lines + line = line.strip() + if line.startswith('#') or not line: + continue + self.filelist.append(line) + manifest.close() + + def check_license(self): + """Checks if license_file' or 'license_files' is configured and adds any + valid paths to 'self.filelist'. + """ + + files = ordered_set.OrderedSet() + + opts = self.distribution.get_option_dict('metadata') + + # ignore the source of the value + _, license_file = opts.get('license_file', (None, None)) + + if license_file is None: + log.debug("'license_file' option was not specified") + else: + files.add(license_file) + + try: + files.update(self.distribution.metadata.license_files) + except TypeError: + log.warn("warning: 'license_files' option is malformed") + + for f in files: + if not os.path.exists(f): + log.warn( + "warning: Failed to find the configured license file '%s'", + f) + files.remove(f) + + self.filelist.extend(files) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/setopt.py b/robot/lib/python3.8/site-packages/setuptools/command/setopt.py new file mode 100644 index 0000000000000000000000000000000000000000..7e57cc02627fc3c3bb49613731a51c72452f96ba --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/setopt.py @@ -0,0 +1,149 @@ +from distutils.util import convert_path +from distutils import log +from distutils.errors import DistutilsOptionError +import distutils +import os + +from setuptools.extern.six.moves import configparser + +from setuptools import Command + +__all__ = ['config_file', 'edit_config', 'option_base', 'setopt'] + + +def config_file(kind="local"): + """Get the filename of the distutils, local, global, or per-user config + + `kind` must be one of "local", "global", or "user" + """ + if kind == 'local': + return 'setup.cfg' + if kind == 'global': + return os.path.join( + os.path.dirname(distutils.__file__), 'distutils.cfg' + ) + if kind == 'user': + dot = os.name == 'posix' and '.' or '' + return os.path.expanduser(convert_path("~/%spydistutils.cfg" % dot)) + raise ValueError( + "config_file() type must be 'local', 'global', or 'user'", kind + ) + + +def edit_config(filename, settings, dry_run=False): + """Edit a configuration file to include `settings` + + `settings` is a dictionary of dictionaries or ``None`` values, keyed by + command/section name. A ``None`` value means to delete the entire section, + while a dictionary lists settings to be changed or deleted in that section. + A setting of ``None`` means to delete that setting. + """ + log.debug("Reading configuration from %s", filename) + opts = configparser.RawConfigParser() + opts.read([filename]) + for section, options in settings.items(): + if options is None: + log.info("Deleting section [%s] from %s", section, filename) + opts.remove_section(section) + else: + if not opts.has_section(section): + log.debug("Adding new section [%s] to %s", section, filename) + opts.add_section(section) + for option, value in options.items(): + if value is None: + log.debug( + "Deleting %s.%s from %s", + section, option, filename + ) + opts.remove_option(section, option) + if not opts.options(section): + log.info("Deleting empty [%s] section from %s", + section, filename) + opts.remove_section(section) + else: + log.debug( + "Setting %s.%s to %r in %s", + section, option, value, filename + ) + opts.set(section, option, value) + + log.info("Writing %s", filename) + if not dry_run: + with open(filename, 'w') as f: + opts.write(f) + + +class option_base(Command): + """Abstract base class for commands that mess with config files""" + + user_options = [ + ('global-config', 'g', + "save options to the site-wide distutils.cfg file"), + ('user-config', 'u', + "save options to the current user's pydistutils.cfg file"), + ('filename=', 'f', + "configuration file to use (default=setup.cfg)"), + ] + + boolean_options = [ + 'global-config', 'user-config', + ] + + def initialize_options(self): + self.global_config = None + self.user_config = None + self.filename = None + + def finalize_options(self): + filenames = [] + if self.global_config: + filenames.append(config_file('global')) + if self.user_config: + filenames.append(config_file('user')) + if self.filename is not None: + filenames.append(self.filename) + if not filenames: + filenames.append(config_file('local')) + if len(filenames) > 1: + raise DistutilsOptionError( + "Must specify only one configuration file option", + filenames + ) + self.filename, = filenames + + +class setopt(option_base): + """Save command-line options to a file""" + + description = "set an option in setup.cfg or another config file" + + user_options = [ + ('command=', 'c', 'command to set an option for'), + ('option=', 'o', 'option to set'), + ('set-value=', 's', 'value of the option'), + ('remove', 'r', 'remove (unset) the value'), + ] + option_base.user_options + + boolean_options = option_base.boolean_options + ['remove'] + + def initialize_options(self): + option_base.initialize_options(self) + self.command = None + self.option = None + self.set_value = None + self.remove = None + + def finalize_options(self): + option_base.finalize_options(self) + if self.command is None or self.option is None: + raise DistutilsOptionError("Must specify --command *and* --option") + if self.set_value is None and not self.remove: + raise DistutilsOptionError("Must specify --set-value or --remove") + + def run(self): + edit_config( + self.filename, { + self.command: {self.option.replace('-', '_'): self.set_value} + }, + self.dry_run + ) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/test.py b/robot/lib/python3.8/site-packages/setuptools/command/test.py new file mode 100644 index 0000000000000000000000000000000000000000..c148b38d10c7691c2045520e5aedb60293dd714d --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/test.py @@ -0,0 +1,279 @@ +import os +import operator +import sys +import contextlib +import itertools +import unittest +from distutils.errors import DistutilsError, DistutilsOptionError +from distutils import log +from unittest import TestLoader + +from setuptools.extern import six +from setuptools.extern.six.moves import map, filter + +from pkg_resources import (resource_listdir, resource_exists, normalize_path, + working_set, _namespace_packages, evaluate_marker, + add_activation_listener, require, EntryPoint) +from setuptools import Command +from .build_py import _unique_everseen + +__metaclass__ = type + + +class ScanningLoader(TestLoader): + + def __init__(self): + TestLoader.__init__(self) + self._visited = set() + + def loadTestsFromModule(self, module, pattern=None): + """Return a suite of all tests cases contained in the given module + + If the module is a package, load tests from all the modules in it. + If the module has an ``additional_tests`` function, call it and add + the return value to the tests. + """ + if module in self._visited: + return None + self._visited.add(module) + + tests = [] + tests.append(TestLoader.loadTestsFromModule(self, module)) + + if hasattr(module, "additional_tests"): + tests.append(module.additional_tests()) + + if hasattr(module, '__path__'): + for file in resource_listdir(module.__name__, ''): + if file.endswith('.py') and file != '__init__.py': + submodule = module.__name__ + '.' + file[:-3] + else: + if resource_exists(module.__name__, file + '/__init__.py'): + submodule = module.__name__ + '.' + file + else: + continue + tests.append(self.loadTestsFromName(submodule)) + + if len(tests) != 1: + return self.suiteClass(tests) + else: + return tests[0] # don't create a nested suite for only one return + + +# adapted from jaraco.classes.properties:NonDataProperty +class NonDataProperty: + def __init__(self, fget): + self.fget = fget + + def __get__(self, obj, objtype=None): + if obj is None: + return self + return self.fget(obj) + + +class test(Command): + """Command to run unit tests after in-place build""" + + description = "run unit tests after in-place build (deprecated)" + + user_options = [ + ('test-module=', 'm', "Run 'test_suite' in specified module"), + ('test-suite=', 's', + "Run single test, case or suite (e.g. 'module.test_suite')"), + ('test-runner=', 'r', "Test runner to use"), + ] + + def initialize_options(self): + self.test_suite = None + self.test_module = None + self.test_loader = None + self.test_runner = None + + def finalize_options(self): + + if self.test_suite and self.test_module: + msg = "You may specify a module or a suite, but not both" + raise DistutilsOptionError(msg) + + if self.test_suite is None: + if self.test_module is None: + self.test_suite = self.distribution.test_suite + else: + self.test_suite = self.test_module + ".test_suite" + + if self.test_loader is None: + self.test_loader = getattr(self.distribution, 'test_loader', None) + if self.test_loader is None: + self.test_loader = "setuptools.command.test:ScanningLoader" + if self.test_runner is None: + self.test_runner = getattr(self.distribution, 'test_runner', None) + + @NonDataProperty + def test_args(self): + return list(self._test_args()) + + def _test_args(self): + if not self.test_suite and sys.version_info >= (2, 7): + yield 'discover' + if self.verbose: + yield '--verbose' + if self.test_suite: + yield self.test_suite + + def with_project_on_sys_path(self, func): + """ + Backward compatibility for project_on_sys_path context. + """ + with self.project_on_sys_path(): + func() + + @contextlib.contextmanager + def project_on_sys_path(self, include_dists=[]): + with_2to3 = six.PY3 and getattr(self.distribution, 'use_2to3', False) + + if with_2to3: + # If we run 2to3 we can not do this inplace: + + # Ensure metadata is up-to-date + self.reinitialize_command('build_py', inplace=0) + self.run_command('build_py') + bpy_cmd = self.get_finalized_command("build_py") + build_path = normalize_path(bpy_cmd.build_lib) + + # Build extensions + self.reinitialize_command('egg_info', egg_base=build_path) + self.run_command('egg_info') + + self.reinitialize_command('build_ext', inplace=0) + self.run_command('build_ext') + else: + # Without 2to3 inplace works fine: + self.run_command('egg_info') + + # Build extensions in-place + self.reinitialize_command('build_ext', inplace=1) + self.run_command('build_ext') + + ei_cmd = self.get_finalized_command("egg_info") + + old_path = sys.path[:] + old_modules = sys.modules.copy() + + try: + project_path = normalize_path(ei_cmd.egg_base) + sys.path.insert(0, project_path) + working_set.__init__() + add_activation_listener(lambda dist: dist.activate()) + require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version)) + with self.paths_on_pythonpath([project_path]): + yield + finally: + sys.path[:] = old_path + sys.modules.clear() + sys.modules.update(old_modules) + working_set.__init__() + + @staticmethod + @contextlib.contextmanager + def paths_on_pythonpath(paths): + """ + Add the indicated paths to the head of the PYTHONPATH environment + variable so that subprocesses will also see the packages at + these paths. + + Do this in a context that restores the value on exit. + """ + nothing = object() + orig_pythonpath = os.environ.get('PYTHONPATH', nothing) + current_pythonpath = os.environ.get('PYTHONPATH', '') + try: + prefix = os.pathsep.join(_unique_everseen(paths)) + to_join = filter(None, [prefix, current_pythonpath]) + new_path = os.pathsep.join(to_join) + if new_path: + os.environ['PYTHONPATH'] = new_path + yield + finally: + if orig_pythonpath is nothing: + os.environ.pop('PYTHONPATH', None) + else: + os.environ['PYTHONPATH'] = orig_pythonpath + + @staticmethod + def install_dists(dist): + """ + Install the requirements indicated by self.distribution and + return an iterable of the dists that were built. + """ + ir_d = dist.fetch_build_eggs(dist.install_requires) + tr_d = dist.fetch_build_eggs(dist.tests_require or []) + er_d = dist.fetch_build_eggs( + v for k, v in dist.extras_require.items() + if k.startswith(':') and evaluate_marker(k[1:]) + ) + return itertools.chain(ir_d, tr_d, er_d) + + def run(self): + self.announce( + "WARNING: Testing via this command is deprecated and will be " + "removed in a future version. Users looking for a generic test " + "entry point independent of test runner are encouraged to use " + "tox.", + log.WARN, + ) + + installed_dists = self.install_dists(self.distribution) + + cmd = ' '.join(self._argv) + if self.dry_run: + self.announce('skipping "%s" (dry run)' % cmd) + return + + self.announce('running "%s"' % cmd) + + paths = map(operator.attrgetter('location'), installed_dists) + with self.paths_on_pythonpath(paths): + with self.project_on_sys_path(): + self.run_tests() + + def run_tests(self): + # Purge modules under test from sys.modules. The test loader will + # re-import them from the build location. Required when 2to3 is used + # with namespace packages. + if six.PY3 and getattr(self.distribution, 'use_2to3', False): + module = self.test_suite.split('.')[0] + if module in _namespace_packages: + del_modules = [] + if module in sys.modules: + del_modules.append(module) + module += '.' + for name in sys.modules: + if name.startswith(module): + del_modules.append(name) + list(map(sys.modules.__delitem__, del_modules)) + + test = unittest.main( + None, None, self._argv, + testLoader=self._resolve_as_ep(self.test_loader), + testRunner=self._resolve_as_ep(self.test_runner), + exit=False, + ) + if not test.result.wasSuccessful(): + msg = 'Test failed: %s' % test.result + self.announce(msg, log.ERROR) + raise DistutilsError(msg) + + @property + def _argv(self): + return ['unittest'] + self.test_args + + @staticmethod + def _resolve_as_ep(val): + """ + Load the indicated attribute value, called, as a as if it were + specified as an entry point. + """ + if val is None: + return + parsed = EntryPoint.parse("x=" + val) + return parsed.resolve()() diff --git a/robot/lib/python3.8/site-packages/setuptools/command/upload.py b/robot/lib/python3.8/site-packages/setuptools/command/upload.py new file mode 100644 index 0000000000000000000000000000000000000000..ec7f81e22772511d668e5ab92f625db33259e803 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/upload.py @@ -0,0 +1,17 @@ +from distutils import log +from distutils.command import upload as orig + +from setuptools.errors import RemovedCommandError + + +class upload(orig.upload): + """Formerly used to upload packages to PyPI.""" + + def run(self): + msg = ( + "The upload command has been removed, use twine to upload " + + "instead (https://pypi.org/p/twine)" + ) + + self.announce("ERROR: " + msg, log.ERROR) + raise RemovedCommandError(msg) diff --git a/robot/lib/python3.8/site-packages/setuptools/command/upload_docs.py b/robot/lib/python3.8/site-packages/setuptools/command/upload_docs.py new file mode 100644 index 0000000000000000000000000000000000000000..07aa564af451ce41d818d72f8ee93cb46887cecf --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/command/upload_docs.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- +"""upload_docs + +Implements a Distutils 'upload_docs' subcommand (upload documentation to +PyPI's pythonhosted.org). +""" + +from base64 import standard_b64encode +from distutils import log +from distutils.errors import DistutilsOptionError +import os +import socket +import zipfile +import tempfile +import shutil +import itertools +import functools + +from setuptools.extern import six +from setuptools.extern.six.moves import http_client, urllib + +from pkg_resources import iter_entry_points +from .upload import upload + + +def _encode(s): + errors = 'surrogateescape' if six.PY3 else 'strict' + return s.encode('utf-8', errors) + + +class upload_docs(upload): + # override the default repository as upload_docs isn't + # supported by Warehouse (and won't be). + DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi/' + + description = 'Upload documentation to PyPI' + + user_options = [ + ('repository=', 'r', + "url of repository [default: %s]" % upload.DEFAULT_REPOSITORY), + ('show-response', None, + 'display full response text from server'), + ('upload-dir=', None, 'directory to upload'), + ] + boolean_options = upload.boolean_options + + def has_sphinx(self): + if self.upload_dir is None: + for ep in iter_entry_points('distutils.commands', 'build_sphinx'): + return True + + sub_commands = [('build_sphinx', has_sphinx)] + + def initialize_options(self): + upload.initialize_options(self) + self.upload_dir = None + self.target_dir = None + + def finalize_options(self): + upload.finalize_options(self) + if self.upload_dir is None: + if self.has_sphinx(): + build_sphinx = self.get_finalized_command('build_sphinx') + self.target_dir = build_sphinx.builder_target_dir + else: + build = self.get_finalized_command('build') + self.target_dir = os.path.join(build.build_base, 'docs') + else: + self.ensure_dirname('upload_dir') + self.target_dir = self.upload_dir + if 'pypi.python.org' in self.repository: + log.warn("Upload_docs command is deprecated. Use RTD instead.") + self.announce('Using upload directory %s' % self.target_dir) + + def create_zipfile(self, filename): + zip_file = zipfile.ZipFile(filename, "w") + try: + self.mkpath(self.target_dir) # just in case + for root, dirs, files in os.walk(self.target_dir): + if root == self.target_dir and not files: + tmpl = "no files found in upload directory '%s'" + raise DistutilsOptionError(tmpl % self.target_dir) + for name in files: + full = os.path.join(root, name) + relative = root[len(self.target_dir):].lstrip(os.path.sep) + dest = os.path.join(relative, name) + zip_file.write(full, dest) + finally: + zip_file.close() + + def run(self): + # Run sub commands + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + tmp_dir = tempfile.mkdtemp() + name = self.distribution.metadata.get_name() + zip_file = os.path.join(tmp_dir, "%s.zip" % name) + try: + self.create_zipfile(zip_file) + self.upload_file(zip_file) + finally: + shutil.rmtree(tmp_dir) + + @staticmethod + def _build_part(item, sep_boundary): + key, values = item + title = '\nContent-Disposition: form-data; name="%s"' % key + # handle multiple entries for the same name + if not isinstance(values, list): + values = [values] + for value in values: + if isinstance(value, tuple): + title += '; filename="%s"' % value[0] + value = value[1] + else: + value = _encode(value) + yield sep_boundary + yield _encode(title) + yield b"\n\n" + yield value + if value and value[-1:] == b'\r': + yield b'\n' # write an extra newline (lurve Macs) + + @classmethod + def _build_multipart(cls, data): + """ + Build up the MIME payload for the POST data + """ + boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = b'\n--' + boundary + end_boundary = sep_boundary + b'--' + end_items = end_boundary, b"\n", + builder = functools.partial( + cls._build_part, + sep_boundary=sep_boundary, + ) + part_groups = map(builder, data.items()) + parts = itertools.chain.from_iterable(part_groups) + body_items = itertools.chain(parts, end_items) + content_type = 'multipart/form-data; boundary=%s' % boundary.decode('ascii') + return b''.join(body_items), content_type + + def upload_file(self, filename): + with open(filename, 'rb') as f: + content = f.read() + meta = self.distribution.metadata + data = { + ':action': 'doc_upload', + 'name': meta.get_name(), + 'content': (os.path.basename(filename), content), + } + # set up the authentication + credentials = _encode(self.username + ':' + self.password) + credentials = standard_b64encode(credentials) + if six.PY3: + credentials = credentials.decode('ascii') + auth = "Basic " + credentials + + body, ct = self._build_multipart(data) + + msg = "Submitting documentation to %s" % (self.repository) + self.announce(msg, log.INFO) + + # build the Request + # We can't use urllib2 since we need to send the Basic + # auth right with the first request + schema, netloc, url, params, query, fragments = \ + urllib.parse.urlparse(self.repository) + assert not params and not query and not fragments + if schema == 'http': + conn = http_client.HTTPConnection(netloc) + elif schema == 'https': + conn = http_client.HTTPSConnection(netloc) + else: + raise AssertionError("unsupported schema " + schema) + + data = '' + try: + conn.connect() + conn.putrequest("POST", url) + content_type = ct + conn.putheader('Content-type', content_type) + conn.putheader('Content-length', str(len(body))) + conn.putheader('Authorization', auth) + conn.endheaders() + conn.send(body) + except socket.error as e: + self.announce(str(e), log.ERROR) + return + + r = conn.getresponse() + if r.status == 200: + msg = 'Server response (%s): %s' % (r.status, r.reason) + self.announce(msg, log.INFO) + elif r.status == 301: + location = r.getheader('Location') + if location is None: + location = 'https://pythonhosted.org/%s/' % meta.get_name() + msg = 'Upload successful. Visit %s' % location + self.announce(msg, log.INFO) + else: + msg = 'Upload failed (%s): %s' % (r.status, r.reason) + self.announce(msg, log.ERROR) + if self.show_response: + print('-' * 75, r.read(), '-' * 75) diff --git a/robot/lib/python3.8/site-packages/setuptools/config.py b/robot/lib/python3.8/site-packages/setuptools/config.py new file mode 100644 index 0000000000000000000000000000000000000000..9b9a0c45e756b44ddea7660228934d0a37fcd97c --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/config.py @@ -0,0 +1,659 @@ +from __future__ import absolute_import, unicode_literals +import io +import os +import sys + +import warnings +import functools +from collections import defaultdict +from functools import partial +from functools import wraps +from importlib import import_module + +from distutils.errors import DistutilsOptionError, DistutilsFileError +from setuptools.extern.packaging.version import LegacyVersion, parse +from setuptools.extern.packaging.specifiers import SpecifierSet +from setuptools.extern.six import string_types, PY3 + + +__metaclass__ = type + + +def read_configuration( + filepath, find_others=False, ignore_option_errors=False): + """Read given configuration file and returns options from it as a dict. + + :param str|unicode filepath: Path to configuration file + to get options from. + + :param bool find_others: Whether to search for other configuration files + which could be on in various places. + + :param bool ignore_option_errors: Whether to silently ignore + options, values of which could not be resolved (e.g. due to exceptions + in directives such as file:, attr:, etc.). + If False exceptions are propagated as expected. + + :rtype: dict + """ + from setuptools.dist import Distribution, _Distribution + + filepath = os.path.abspath(filepath) + + if not os.path.isfile(filepath): + raise DistutilsFileError( + 'Configuration file %s does not exist.' % filepath) + + current_directory = os.getcwd() + os.chdir(os.path.dirname(filepath)) + + try: + dist = Distribution() + + filenames = dist.find_config_files() if find_others else [] + if filepath not in filenames: + filenames.append(filepath) + + _Distribution.parse_config_files(dist, filenames=filenames) + + handlers = parse_configuration( + dist, dist.command_options, + ignore_option_errors=ignore_option_errors) + + finally: + os.chdir(current_directory) + + return configuration_to_dict(handlers) + + +def _get_option(target_obj, key): + """ + Given a target object and option key, get that option from + the target object, either through a get_{key} method or + from an attribute directly. + """ + getter_name = 'get_{key}'.format(**locals()) + by_attribute = functools.partial(getattr, target_obj, key) + getter = getattr(target_obj, getter_name, by_attribute) + return getter() + + +def configuration_to_dict(handlers): + """Returns configuration data gathered by given handlers as a dict. + + :param list[ConfigHandler] handlers: Handlers list, + usually from parse_configuration() + + :rtype: dict + """ + config_dict = defaultdict(dict) + + for handler in handlers: + for option in handler.set_options: + value = _get_option(handler.target_obj, option) + config_dict[handler.section_prefix][option] = value + + return config_dict + + +def parse_configuration( + distribution, command_options, ignore_option_errors=False): + """Performs additional parsing of configuration options + for a distribution. + + Returns a list of used option handlers. + + :param Distribution distribution: + :param dict command_options: + :param bool ignore_option_errors: Whether to silently ignore + options, values of which could not be resolved (e.g. due to exceptions + in directives such as file:, attr:, etc.). + If False exceptions are propagated as expected. + :rtype: list + """ + options = ConfigOptionsHandler( + distribution, command_options, ignore_option_errors) + options.parse() + + meta = ConfigMetadataHandler( + distribution.metadata, command_options, ignore_option_errors, + distribution.package_dir) + meta.parse() + + return meta, options + + +class ConfigHandler: + """Handles metadata supplied in configuration files.""" + + section_prefix = None + """Prefix for config sections handled by this handler. + Must be provided by class heirs. + + """ + + aliases = {} + """Options aliases. + For compatibility with various packages. E.g.: d2to1 and pbr. + Note: `-` in keys is replaced with `_` by config parser. + + """ + + def __init__(self, target_obj, options, ignore_option_errors=False): + sections = {} + + section_prefix = self.section_prefix + for section_name, section_options in options.items(): + if not section_name.startswith(section_prefix): + continue + + section_name = section_name.replace(section_prefix, '').strip('.') + sections[section_name] = section_options + + self.ignore_option_errors = ignore_option_errors + self.target_obj = target_obj + self.sections = sections + self.set_options = [] + + @property + def parsers(self): + """Metadata item name to parser function mapping.""" + raise NotImplementedError( + '%s must provide .parsers property' % self.__class__.__name__) + + def __setitem__(self, option_name, value): + unknown = tuple() + target_obj = self.target_obj + + # Translate alias into real name. + option_name = self.aliases.get(option_name, option_name) + + current_value = getattr(target_obj, option_name, unknown) + + if current_value is unknown: + raise KeyError(option_name) + + if current_value: + # Already inhabited. Skipping. + return + + skip_option = False + parser = self.parsers.get(option_name) + if parser: + try: + value = parser(value) + + except Exception: + skip_option = True + if not self.ignore_option_errors: + raise + + if skip_option: + return + + setter = getattr(target_obj, 'set_%s' % option_name, None) + if setter is None: + setattr(target_obj, option_name, value) + else: + setter(value) + + self.set_options.append(option_name) + + @classmethod + def _parse_list(cls, value, separator=','): + """Represents value as a list. + + Value is split either by separator (defaults to comma) or by lines. + + :param value: + :param separator: List items separator character. + :rtype: list + """ + if isinstance(value, list): # _get_parser_compound case + return value + + if '\n' in value: + value = value.splitlines() + else: + value = value.split(separator) + + return [chunk.strip() for chunk in value if chunk.strip()] + + @classmethod + def _parse_dict(cls, value): + """Represents value as a dict. + + :param value: + :rtype: dict + """ + separator = '=' + result = {} + for line in cls._parse_list(value): + key, sep, val = line.partition(separator) + if sep != separator: + raise DistutilsOptionError( + 'Unable to parse option value to dict: %s' % value) + result[key.strip()] = val.strip() + + return result + + @classmethod + def _parse_bool(cls, value): + """Represents value as boolean. + + :param value: + :rtype: bool + """ + value = value.lower() + return value in ('1', 'true', 'yes') + + @classmethod + def _exclude_files_parser(cls, key): + """Returns a parser function to make sure field inputs + are not files. + + Parses a value after getting the key so error messages are + more informative. + + :param key: + :rtype: callable + """ + def parser(value): + exclude_directive = 'file:' + if value.startswith(exclude_directive): + raise ValueError( + 'Only strings are accepted for the {0} field, ' + 'files are not accepted'.format(key)) + return value + return parser + + @classmethod + def _parse_file(cls, value): + """Represents value as a string, allowing including text + from nearest files using `file:` directive. + + Directive is sandboxed and won't reach anything outside + directory with setup.py. + + Examples: + file: README.rst, CHANGELOG.md, src/file.txt + + :param str value: + :rtype: str + """ + include_directive = 'file:' + + if not isinstance(value, string_types): + return value + + if not value.startswith(include_directive): + return value + + spec = value[len(include_directive):] + filepaths = (os.path.abspath(path.strip()) for path in spec.split(',')) + return '\n'.join( + cls._read_file(path) + for path in filepaths + if (cls._assert_local(path) or True) + and os.path.isfile(path) + ) + + @staticmethod + def _assert_local(filepath): + if not filepath.startswith(os.getcwd()): + raise DistutilsOptionError( + '`file:` directive can not access %s' % filepath) + + @staticmethod + def _read_file(filepath): + with io.open(filepath, encoding='utf-8') as f: + return f.read() + + @classmethod + def _parse_attr(cls, value, package_dir=None): + """Represents value as a module attribute. + + Examples: + attr: package.attr + attr: package.module.attr + + :param str value: + :rtype: str + """ + attr_directive = 'attr:' + if not value.startswith(attr_directive): + return value + + attrs_path = value.replace(attr_directive, '').strip().split('.') + attr_name = attrs_path.pop() + + module_name = '.'.join(attrs_path) + module_name = module_name or '__init__' + + parent_path = os.getcwd() + if package_dir: + if attrs_path[0] in package_dir: + # A custom path was specified for the module we want to import + custom_path = package_dir[attrs_path[0]] + parts = custom_path.rsplit('/', 1) + if len(parts) > 1: + parent_path = os.path.join(os.getcwd(), parts[0]) + module_name = parts[1] + else: + module_name = custom_path + elif '' in package_dir: + # A custom parent directory was specified for all root modules + parent_path = os.path.join(os.getcwd(), package_dir['']) + sys.path.insert(0, parent_path) + try: + module = import_module(module_name) + value = getattr(module, attr_name) + + finally: + sys.path = sys.path[1:] + + return value + + @classmethod + def _get_parser_compound(cls, *parse_methods): + """Returns parser function to represents value as a list. + + Parses a value applying given methods one after another. + + :param parse_methods: + :rtype: callable + """ + def parse(value): + parsed = value + + for method in parse_methods: + parsed = method(parsed) + + return parsed + + return parse + + @classmethod + def _parse_section_to_dict(cls, section_options, values_parser=None): + """Parses section options into a dictionary. + + Optionally applies a given parser to values. + + :param dict section_options: + :param callable values_parser: + :rtype: dict + """ + value = {} + values_parser = values_parser or (lambda val: val) + for key, (_, val) in section_options.items(): + value[key] = values_parser(val) + return value + + def parse_section(self, section_options): + """Parses configuration file section. + + :param dict section_options: + """ + for (name, (_, value)) in section_options.items(): + try: + self[name] = value + + except KeyError: + pass # Keep silent for a new option may appear anytime. + + def parse(self): + """Parses configuration file items from one + or more related sections. + + """ + for section_name, section_options in self.sections.items(): + + method_postfix = '' + if section_name: # [section.option] variant + method_postfix = '_%s' % section_name + + section_parser_method = getattr( + self, + # Dots in section names are translated into dunderscores. + ('parse_section%s' % method_postfix).replace('.', '__'), + None) + + if section_parser_method is None: + raise DistutilsOptionError( + 'Unsupported distribution option section: [%s.%s]' % ( + self.section_prefix, section_name)) + + section_parser_method(section_options) + + def _deprecated_config_handler(self, func, msg, warning_class): + """ this function will wrap around parameters that are deprecated + + :param msg: deprecation message + :param warning_class: class of warning exception to be raised + :param func: function to be wrapped around + """ + @wraps(func) + def config_handler(*args, **kwargs): + warnings.warn(msg, warning_class) + return func(*args, **kwargs) + + return config_handler + + +class ConfigMetadataHandler(ConfigHandler): + + section_prefix = 'metadata' + + aliases = { + 'home_page': 'url', + 'summary': 'description', + 'classifier': 'classifiers', + 'platform': 'platforms', + } + + strict_mode = False + """We need to keep it loose, to be partially compatible with + `pbr` and `d2to1` packages which also uses `metadata` section. + + """ + + def __init__(self, target_obj, options, ignore_option_errors=False, + package_dir=None): + super(ConfigMetadataHandler, self).__init__(target_obj, options, + ignore_option_errors) + self.package_dir = package_dir + + @property + def parsers(self): + """Metadata item name to parser function mapping.""" + parse_list = self._parse_list + parse_file = self._parse_file + parse_dict = self._parse_dict + exclude_files_parser = self._exclude_files_parser + + return { + 'platforms': parse_list, + 'keywords': parse_list, + 'provides': parse_list, + 'requires': self._deprecated_config_handler( + parse_list, + "The requires parameter is deprecated, please use " + "install_requires for runtime dependencies.", + DeprecationWarning), + 'obsoletes': parse_list, + 'classifiers': self._get_parser_compound(parse_file, parse_list), + 'license': exclude_files_parser('license'), + 'license_files': parse_list, + 'description': parse_file, + 'long_description': parse_file, + 'version': self._parse_version, + 'project_urls': parse_dict, + } + + def _parse_version(self, value): + """Parses `version` option value. + + :param value: + :rtype: str + + """ + version = self._parse_file(value) + + if version != value: + version = version.strip() + # Be strict about versions loaded from file because it's easy to + # accidentally include newlines and other unintended content + if isinstance(parse(version), LegacyVersion): + tmpl = ( + 'Version loaded from {value} does not ' + 'comply with PEP 440: {version}' + ) + raise DistutilsOptionError(tmpl.format(**locals())) + + return version + + version = self._parse_attr(value, self.package_dir) + + if callable(version): + version = version() + + if not isinstance(version, string_types): + if hasattr(version, '__iter__'): + version = '.'.join(map(str, version)) + else: + version = '%s' % version + + return version + + +class ConfigOptionsHandler(ConfigHandler): + + section_prefix = 'options' + + @property + def parsers(self): + """Metadata item name to parser function mapping.""" + parse_list = self._parse_list + parse_list_semicolon = partial(self._parse_list, separator=';') + parse_bool = self._parse_bool + parse_dict = self._parse_dict + + return { + 'zip_safe': parse_bool, + 'use_2to3': parse_bool, + 'include_package_data': parse_bool, + 'package_dir': parse_dict, + 'use_2to3_fixers': parse_list, + 'use_2to3_exclude_fixers': parse_list, + 'convert_2to3_doctests': parse_list, + 'scripts': parse_list, + 'eager_resources': parse_list, + 'dependency_links': parse_list, + 'namespace_packages': parse_list, + 'install_requires': parse_list_semicolon, + 'setup_requires': parse_list_semicolon, + 'tests_require': parse_list_semicolon, + 'packages': self._parse_packages, + 'entry_points': self._parse_file, + 'py_modules': parse_list, + 'python_requires': SpecifierSet, + } + + def _parse_packages(self, value): + """Parses `packages` option value. + + :param value: + :rtype: list + """ + find_directives = ['find:', 'find_namespace:'] + trimmed_value = value.strip() + + if trimmed_value not in find_directives: + return self._parse_list(value) + + findns = trimmed_value == find_directives[1] + if findns and not PY3: + raise DistutilsOptionError( + 'find_namespace: directive is unsupported on Python < 3.3') + + # Read function arguments from a dedicated section. + find_kwargs = self.parse_section_packages__find( + self.sections.get('packages.find', {})) + + if findns: + from setuptools import find_namespace_packages as find_packages + else: + from setuptools import find_packages + + return find_packages(**find_kwargs) + + def parse_section_packages__find(self, section_options): + """Parses `packages.find` configuration file section. + + To be used in conjunction with _parse_packages(). + + :param dict section_options: + """ + section_data = self._parse_section_to_dict( + section_options, self._parse_list) + + valid_keys = ['where', 'include', 'exclude'] + + find_kwargs = dict( + [(k, v) for k, v in section_data.items() if k in valid_keys and v]) + + where = find_kwargs.get('where') + if where is not None: + find_kwargs['where'] = where[0] # cast list to single val + + return find_kwargs + + def parse_section_entry_points(self, section_options): + """Parses `entry_points` configuration file section. + + :param dict section_options: + """ + parsed = self._parse_section_to_dict(section_options, self._parse_list) + self['entry_points'] = parsed + + def _parse_package_data(self, section_options): + parsed = self._parse_section_to_dict(section_options, self._parse_list) + + root = parsed.get('*') + if root: + parsed[''] = root + del parsed['*'] + + return parsed + + def parse_section_package_data(self, section_options): + """Parses `package_data` configuration file section. + + :param dict section_options: + """ + self['package_data'] = self._parse_package_data(section_options) + + def parse_section_exclude_package_data(self, section_options): + """Parses `exclude_package_data` configuration file section. + + :param dict section_options: + """ + self['exclude_package_data'] = self._parse_package_data( + section_options) + + def parse_section_extras_require(self, section_options): + """Parses `extras_require` configuration file section. + + :param dict section_options: + """ + parse_list = partial(self._parse_list, separator=';') + self['extras_require'] = self._parse_section_to_dict( + section_options, parse_list) + + def parse_section_data_files(self, section_options): + """Parses `data_files` configuration file section. + + :param dict section_options: + """ + parsed = self._parse_section_to_dict(section_options, self._parse_list) + self['data_files'] = [(k, v) for k, v in parsed.items()] diff --git a/robot/lib/python3.8/site-packages/setuptools/dep_util.py b/robot/lib/python3.8/site-packages/setuptools/dep_util.py new file mode 100644 index 0000000000000000000000000000000000000000..2931c13ec35aa60b742ac4c46ceabd4ed32a5511 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/dep_util.py @@ -0,0 +1,23 @@ +from distutils.dep_util import newer_group + +# yes, this is was almost entirely copy-pasted from +# 'newer_pairwise()', this is just another convenience +# function. +def newer_pairwise_group(sources_groups, targets): + """Walk both arguments in parallel, testing if each source group is newer + than its corresponding target. Returns a pair of lists (sources_groups, + targets) where sources is newer than target, according to the semantics + of 'newer_group()'. + """ + if len(sources_groups) != len(targets): + raise ValueError("'sources_group' and 'targets' must be the same length") + + # build a pair of lists (sources_groups, targets) where source is newer + n_sources = [] + n_targets = [] + for i in range(len(sources_groups)): + if newer_group(sources_groups[i], targets[i]): + n_sources.append(sources_groups[i]) + n_targets.append(targets[i]) + + return n_sources, n_targets diff --git a/robot/lib/python3.8/site-packages/setuptools/depends.py b/robot/lib/python3.8/site-packages/setuptools/depends.py new file mode 100644 index 0000000000000000000000000000000000000000..a37675cbd9bc9583fd01cc158198e2f4deda321b --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/depends.py @@ -0,0 +1,176 @@ +import sys +import marshal +import contextlib +from distutils.version import StrictVersion + +from .py33compat import Bytecode + +from .py27compat import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE +from . import py27compat + + +__all__ = [ + 'Require', 'find_module', 'get_module_constant', 'extract_constant' +] + + +class Require: + """A prerequisite to building or installing a distribution""" + + def __init__( + self, name, requested_version, module, homepage='', + attribute=None, format=None): + + if format is None and requested_version is not None: + format = StrictVersion + + if format is not None: + requested_version = format(requested_version) + if attribute is None: + attribute = '__version__' + + self.__dict__.update(locals()) + del self.self + + def full_name(self): + """Return full package/distribution name, w/version""" + if self.requested_version is not None: + return '%s-%s' % (self.name, self.requested_version) + return self.name + + def version_ok(self, version): + """Is 'version' sufficiently up-to-date?""" + return self.attribute is None or self.format is None or \ + str(version) != "unknown" and version >= self.requested_version + + def get_version(self, paths=None, default="unknown"): + """Get version number of installed module, 'None', or 'default' + + Search 'paths' for module. If not found, return 'None'. If found, + return the extracted version attribute, or 'default' if no version + attribute was specified, or the value cannot be determined without + importing the module. The version is formatted according to the + requirement's version format (if any), unless it is 'None' or the + supplied 'default'. + """ + + if self.attribute is None: + try: + f, p, i = find_module(self.module, paths) + if f: + f.close() + return default + except ImportError: + return None + + v = get_module_constant(self.module, self.attribute, default, paths) + + if v is not None and v is not default and self.format is not None: + return self.format(v) + + return v + + def is_present(self, paths=None): + """Return true if dependency is present on 'paths'""" + return self.get_version(paths) is not None + + def is_current(self, paths=None): + """Return true if dependency is present and up-to-date on 'paths'""" + version = self.get_version(paths) + if version is None: + return False + return self.version_ok(version) + + +def maybe_close(f): + @contextlib.contextmanager + def empty(): + yield + return + if not f: + return empty() + + return contextlib.closing(f) + + +def get_module_constant(module, symbol, default=-1, paths=None): + """Find 'module' by searching 'paths', and extract 'symbol' + + Return 'None' if 'module' does not exist on 'paths', or it does not define + 'symbol'. If the module defines 'symbol' as a constant, return the + constant. Otherwise, return 'default'.""" + + try: + f, path, (suffix, mode, kind) = info = find_module(module, paths) + except ImportError: + # Module doesn't exist + return None + + with maybe_close(f): + if kind == PY_COMPILED: + f.read(8) # skip magic & date + code = marshal.load(f) + elif kind == PY_FROZEN: + code = py27compat.get_frozen_object(module, paths) + elif kind == PY_SOURCE: + code = compile(f.read(), path, 'exec') + else: + # Not something we can parse; we'll have to import it. :( + imported = py27compat.get_module(module, paths, info) + return getattr(imported, symbol, None) + + return extract_constant(code, symbol, default) + + +def extract_constant(code, symbol, default=-1): + """Extract the constant value of 'symbol' from 'code' + + If the name 'symbol' is bound to a constant value by the Python code + object 'code', return that value. If 'symbol' is bound to an expression, + return 'default'. Otherwise, return 'None'. + + Return value is based on the first assignment to 'symbol'. 'symbol' must + be a global, or at least a non-"fast" local in the code block. That is, + only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol' + must be present in 'code.co_names'. + """ + if symbol not in code.co_names: + # name's not there, can't possibly be an assignment + return None + + name_idx = list(code.co_names).index(symbol) + + STORE_NAME = 90 + STORE_GLOBAL = 97 + LOAD_CONST = 100 + + const = default + + for byte_code in Bytecode(code): + op = byte_code.opcode + arg = byte_code.arg + + if op == LOAD_CONST: + const = code.co_consts[arg] + elif arg == name_idx and (op == STORE_NAME or op == STORE_GLOBAL): + return const + else: + const = default + + +def _update_globals(): + """ + Patch the globals to remove the objects not available on some platforms. + + XXX it'd be better to test assertions about bytecode instead. + """ + + if not sys.platform.startswith('java') and sys.platform != 'cli': + return + incompatible = 'extract_constant', 'get_module_constant' + for name in incompatible: + del globals()[name] + __all__.remove(name) + + +_update_globals() diff --git a/robot/lib/python3.8/site-packages/setuptools/dist.py b/robot/lib/python3.8/site-packages/setuptools/dist.py new file mode 100644 index 0000000000000000000000000000000000000000..f22429e8e191683da2cc83c7cc5eba205a541988 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/dist.py @@ -0,0 +1,1274 @@ +# -*- coding: utf-8 -*- +__all__ = ['Distribution'] + +import io +import sys +import re +import os +import warnings +import numbers +import distutils.log +import distutils.core +import distutils.cmd +import distutils.dist +from distutils.util import strtobool +from distutils.debug import DEBUG +from distutils.fancy_getopt import translate_longopt +import itertools + +from collections import defaultdict +from email import message_from_file + +from distutils.errors import ( + DistutilsOptionError, DistutilsPlatformError, DistutilsSetupError, +) +from distutils.util import rfc822_escape +from distutils.version import StrictVersion + +from setuptools.extern import six +from setuptools.extern import packaging +from setuptools.extern import ordered_set +from setuptools.extern.six.moves import map, filter, filterfalse + +from . import SetuptoolsDeprecationWarning + +from setuptools.depends import Require +from setuptools import windows_support +from setuptools.monkey import get_unpatched +from setuptools.config import parse_configuration +import pkg_resources + +__import__('setuptools.extern.packaging.specifiers') +__import__('setuptools.extern.packaging.version') + + +def _get_unpatched(cls): + warnings.warn("Do not call this function", DistDeprecationWarning) + return get_unpatched(cls) + + +def get_metadata_version(self): + mv = getattr(self, 'metadata_version', None) + + if mv is None: + if self.long_description_content_type or self.provides_extras: + mv = StrictVersion('2.1') + elif (self.maintainer is not None or + self.maintainer_email is not None or + getattr(self, 'python_requires', None) is not None or + self.project_urls): + mv = StrictVersion('1.2') + elif (self.provides or self.requires or self.obsoletes or + self.classifiers or self.download_url): + mv = StrictVersion('1.1') + else: + mv = StrictVersion('1.0') + + self.metadata_version = mv + + return mv + + +def read_pkg_file(self, file): + """Reads the metadata values from a file object.""" + msg = message_from_file(file) + + def _read_field(name): + value = msg[name] + if value == 'UNKNOWN': + return None + return value + + def _read_list(name): + values = msg.get_all(name, None) + if values == []: + return None + return values + + self.metadata_version = StrictVersion(msg['metadata-version']) + self.name = _read_field('name') + self.version = _read_field('version') + self.description = _read_field('summary') + # we are filling author only. + self.author = _read_field('author') + self.maintainer = None + self.author_email = _read_field('author-email') + self.maintainer_email = None + self.url = _read_field('home-page') + self.license = _read_field('license') + + if 'download-url' in msg: + self.download_url = _read_field('download-url') + else: + self.download_url = None + + self.long_description = _read_field('description') + self.description = _read_field('summary') + + if 'keywords' in msg: + self.keywords = _read_field('keywords').split(',') + + self.platforms = _read_list('platform') + self.classifiers = _read_list('classifier') + + # PEP 314 - these fields only exist in 1.1 + if self.metadata_version == StrictVersion('1.1'): + self.requires = _read_list('requires') + self.provides = _read_list('provides') + self.obsoletes = _read_list('obsoletes') + else: + self.requires = None + self.provides = None + self.obsoletes = None + + +# Based on Python 3.5 version +def write_pkg_file(self, file): + """Write the PKG-INFO format data to a file object. + """ + version = self.get_metadata_version() + + if six.PY2: + def write_field(key, value): + file.write("%s: %s\n" % (key, self._encode_field(value))) + else: + def write_field(key, value): + file.write("%s: %s\n" % (key, value)) + + write_field('Metadata-Version', str(version)) + write_field('Name', self.get_name()) + write_field('Version', self.get_version()) + write_field('Summary', self.get_description()) + write_field('Home-page', self.get_url()) + + if version < StrictVersion('1.2'): + write_field('Author', self.get_contact()) + write_field('Author-email', self.get_contact_email()) + else: + optional_fields = ( + ('Author', 'author'), + ('Author-email', 'author_email'), + ('Maintainer', 'maintainer'), + ('Maintainer-email', 'maintainer_email'), + ) + + for field, attr in optional_fields: + attr_val = getattr(self, attr) + + if attr_val is not None: + write_field(field, attr_val) + + write_field('License', self.get_license()) + if self.download_url: + write_field('Download-URL', self.download_url) + for project_url in self.project_urls.items(): + write_field('Project-URL', '%s, %s' % project_url) + + long_desc = rfc822_escape(self.get_long_description()) + write_field('Description', long_desc) + + keywords = ','.join(self.get_keywords()) + if keywords: + write_field('Keywords', keywords) + + if version >= StrictVersion('1.2'): + for platform in self.get_platforms(): + write_field('Platform', platform) + else: + self._write_list(file, 'Platform', self.get_platforms()) + + self._write_list(file, 'Classifier', self.get_classifiers()) + + # PEP 314 + self._write_list(file, 'Requires', self.get_requires()) + self._write_list(file, 'Provides', self.get_provides()) + self._write_list(file, 'Obsoletes', self.get_obsoletes()) + + # Setuptools specific for PEP 345 + if hasattr(self, 'python_requires'): + write_field('Requires-Python', self.python_requires) + + # PEP 566 + if self.long_description_content_type: + write_field( + 'Description-Content-Type', + self.long_description_content_type + ) + if self.provides_extras: + for extra in sorted(self.provides_extras): + write_field('Provides-Extra', extra) + + +sequence = tuple, list + + +def check_importable(dist, attr, value): + try: + ep = pkg_resources.EntryPoint.parse('x=' + value) + assert not ep.extras + except (TypeError, ValueError, AttributeError, AssertionError): + raise DistutilsSetupError( + "%r must be importable 'module:attrs' string (got %r)" + % (attr, value) + ) + + +def assert_string_list(dist, attr, value): + """Verify that value is a string list""" + try: + # verify that value is a list or tuple to exclude unordered + # or single-use iterables + assert isinstance(value, (list, tuple)) + # verify that elements of value are strings + assert ''.join(value) != value + except (TypeError, ValueError, AttributeError, AssertionError): + raise DistutilsSetupError( + "%r must be a list of strings (got %r)" % (attr, value) + ) + + +def check_nsp(dist, attr, value): + """Verify that namespace packages are valid""" + ns_packages = value + assert_string_list(dist, attr, ns_packages) + for nsp in ns_packages: + if not dist.has_contents_for(nsp): + raise DistutilsSetupError( + "Distribution contains no modules or packages for " + + "namespace package %r" % nsp + ) + parent, sep, child = nsp.rpartition('.') + if parent and parent not in ns_packages: + distutils.log.warn( + "WARNING: %r is declared as a package namespace, but %r" + " is not: please correct this in setup.py", nsp, parent + ) + + +def check_extras(dist, attr, value): + """Verify that extras_require mapping is valid""" + try: + list(itertools.starmap(_check_extra, value.items())) + except (TypeError, ValueError, AttributeError): + raise DistutilsSetupError( + "'extras_require' must be a dictionary whose values are " + "strings or lists of strings containing valid project/version " + "requirement specifiers." + ) + + +def _check_extra(extra, reqs): + name, sep, marker = extra.partition(':') + if marker and pkg_resources.invalid_marker(marker): + raise DistutilsSetupError("Invalid environment marker: " + marker) + list(pkg_resources.parse_requirements(reqs)) + + +def assert_bool(dist, attr, value): + """Verify that value is True, False, 0, or 1""" + if bool(value) != value: + tmpl = "{attr!r} must be a boolean value (got {value!r})" + raise DistutilsSetupError(tmpl.format(attr=attr, value=value)) + + +def check_requirements(dist, attr, value): + """Verify that install_requires is a valid requirements list""" + try: + list(pkg_resources.parse_requirements(value)) + if isinstance(value, (dict, set)): + raise TypeError("Unordered types are not allowed") + except (TypeError, ValueError) as error: + tmpl = ( + "{attr!r} must be a string or list of strings " + "containing valid project/version requirement specifiers; {error}" + ) + raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) + + +def check_specifier(dist, attr, value): + """Verify that value is a valid version specifier""" + try: + packaging.specifiers.SpecifierSet(value) + except packaging.specifiers.InvalidSpecifier as error: + tmpl = ( + "{attr!r} must be a string " + "containing valid version specifiers; {error}" + ) + raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) + + +def check_entry_points(dist, attr, value): + """Verify that entry_points map is parseable""" + try: + pkg_resources.EntryPoint.parse_map(value) + except ValueError as e: + raise DistutilsSetupError(e) + + +def check_test_suite(dist, attr, value): + if not isinstance(value, six.string_types): + raise DistutilsSetupError("test_suite must be a string") + + +def check_package_data(dist, attr, value): + """Verify that value is a dictionary of package names to glob lists""" + if not isinstance(value, dict): + raise DistutilsSetupError( + "{!r} must be a dictionary mapping package names to lists of " + "string wildcard patterns".format(attr)) + for k, v in value.items(): + if not isinstance(k, six.string_types): + raise DistutilsSetupError( + "keys of {!r} dict must be strings (got {!r})" + .format(attr, k) + ) + assert_string_list(dist, 'values of {!r} dict'.format(attr), v) + + +def check_packages(dist, attr, value): + for pkgname in value: + if not re.match(r'\w+(\.\w+)*', pkgname): + distutils.log.warn( + "WARNING: %r not a valid package name; please use only " + ".-separated package names in setup.py", pkgname + ) + + +_Distribution = get_unpatched(distutils.core.Distribution) + + +class Distribution(_Distribution): + """Distribution with support for features, tests, and package data + + This is an enhanced version of 'distutils.dist.Distribution' that + effectively adds the following new optional keyword arguments to 'setup()': + + 'install_requires' -- a string or sequence of strings specifying project + versions that the distribution requires when installed, in the format + used by 'pkg_resources.require()'. They will be installed + automatically when the package is installed. If you wish to use + packages that are not available in PyPI, or want to give your users an + alternate download location, you can add a 'find_links' option to the + '[easy_install]' section of your project's 'setup.cfg' file, and then + setuptools will scan the listed web pages for links that satisfy the + requirements. + + 'extras_require' -- a dictionary mapping names of optional "extras" to the + additional requirement(s) that using those extras incurs. For example, + this:: + + extras_require = dict(reST = ["docutils>=0.3", "reSTedit"]) + + indicates that the distribution can optionally provide an extra + capability called "reST", but it can only be used if docutils and + reSTedit are installed. If the user installs your package using + EasyInstall and requests one of your extras, the corresponding + additional requirements will be installed if needed. + + 'features' **deprecated** -- a dictionary mapping option names to + 'setuptools.Feature' + objects. Features are a portion of the distribution that can be + included or excluded based on user options, inter-feature dependencies, + and availability on the current system. Excluded features are omitted + from all setup commands, including source and binary distributions, so + you can create multiple distributions from the same source tree. + Feature names should be valid Python identifiers, except that they may + contain the '-' (minus) sign. Features can be included or excluded + via the command line options '--with-X' and '--without-X', where 'X' is + the name of the feature. Whether a feature is included by default, and + whether you are allowed to control this from the command line, is + determined by the Feature object. See the 'Feature' class for more + information. + + 'test_suite' -- the name of a test suite to run for the 'test' command. + If the user runs 'python setup.py test', the package will be installed, + and the named test suite will be run. The format is the same as + would be used on a 'unittest.py' command line. That is, it is the + dotted name of an object to import and call to generate a test suite. + + 'package_data' -- a dictionary mapping package names to lists of filenames + or globs to use to find data files contained in the named packages. + If the dictionary has filenames or globs listed under '""' (the empty + string), those names will be searched for in every package, in addition + to any names for the specific package. Data files found using these + names/globs will be installed along with the package, in the same + location as the package. Note that globs are allowed to reference + the contents of non-package subdirectories, as long as you use '/' as + a path separator. (Globs are automatically converted to + platform-specific paths at runtime.) + + In addition to these new keywords, this class also has several new methods + for manipulating the distribution's contents. For example, the 'include()' + and 'exclude()' methods can be thought of as in-place add and subtract + commands that add or remove packages, modules, extensions, and so on from + the distribution. They are used by the feature subsystem to configure the + distribution for the included and excluded features. + """ + + _DISTUTILS_UNSUPPORTED_METADATA = { + 'long_description_content_type': None, + 'project_urls': dict, + 'provides_extras': ordered_set.OrderedSet, + 'license_files': ordered_set.OrderedSet, + } + + _patched_dist = None + + def patch_missing_pkg_info(self, attrs): + # Fake up a replacement for the data that would normally come from + # PKG-INFO, but which might not yet be built if this is a fresh + # checkout. + # + if not attrs or 'name' not in attrs or 'version' not in attrs: + return + key = pkg_resources.safe_name(str(attrs['name'])).lower() + dist = pkg_resources.working_set.by_key.get(key) + if dist is not None and not dist.has_metadata('PKG-INFO'): + dist._version = pkg_resources.safe_version(str(attrs['version'])) + self._patched_dist = dist + + def __init__(self, attrs=None): + have_package_data = hasattr(self, "package_data") + if not have_package_data: + self.package_data = {} + attrs = attrs or {} + if 'features' in attrs or 'require_features' in attrs: + Feature.warn_deprecated() + self.require_features = [] + self.features = {} + self.dist_files = [] + # Filter-out setuptools' specific options. + self.src_root = attrs.pop("src_root", None) + self.patch_missing_pkg_info(attrs) + self.dependency_links = attrs.pop('dependency_links', []) + self.setup_requires = attrs.pop('setup_requires', []) + for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): + vars(self).setdefault(ep.name, None) + _Distribution.__init__(self, { + k: v for k, v in attrs.items() + if k not in self._DISTUTILS_UNSUPPORTED_METADATA + }) + + # Fill-in missing metadata fields not supported by distutils. + # Note some fields may have been set by other tools (e.g. pbr) + # above; they are taken preferrentially to setup() arguments + for option, default in self._DISTUTILS_UNSUPPORTED_METADATA.items(): + for source in self.metadata.__dict__, attrs: + if option in source: + value = source[option] + break + else: + value = default() if default else None + setattr(self.metadata, option, value) + + if isinstance(self.metadata.version, numbers.Number): + # Some people apparently take "version number" too literally :) + self.metadata.version = str(self.metadata.version) + + if self.metadata.version is not None: + try: + ver = packaging.version.Version(self.metadata.version) + normalized_version = str(ver) + if self.metadata.version != normalized_version: + warnings.warn( + "Normalizing '%s' to '%s'" % ( + self.metadata.version, + normalized_version, + ) + ) + self.metadata.version = normalized_version + except (packaging.version.InvalidVersion, TypeError): + warnings.warn( + "The version specified (%r) is an invalid version, this " + "may not work as expected with newer versions of " + "setuptools, pip, and PyPI. Please see PEP 440 for more " + "details." % self.metadata.version + ) + self._finalize_requires() + + def _finalize_requires(self): + """ + Set `metadata.python_requires` and fix environment markers + in `install_requires` and `extras_require`. + """ + if getattr(self, 'python_requires', None): + self.metadata.python_requires = self.python_requires + + if getattr(self, 'extras_require', None): + for extra in self.extras_require.keys(): + # Since this gets called multiple times at points where the + # keys have become 'converted' extras, ensure that we are only + # truly adding extras we haven't seen before here. + extra = extra.split(':')[0] + if extra: + self.metadata.provides_extras.add(extra) + + self._convert_extras_requirements() + self._move_install_requirements_markers() + + def _convert_extras_requirements(self): + """ + Convert requirements in `extras_require` of the form + `"extra": ["barbazquux; {marker}"]` to + `"extra:{marker}": ["barbazquux"]`. + """ + spec_ext_reqs = getattr(self, 'extras_require', None) or {} + self._tmp_extras_require = defaultdict(list) + for section, v in spec_ext_reqs.items(): + # Do not strip empty sections. + self._tmp_extras_require[section] + for r in pkg_resources.parse_requirements(v): + suffix = self._suffix_for(r) + self._tmp_extras_require[section + suffix].append(r) + + @staticmethod + def _suffix_for(req): + """ + For a requirement, return the 'extras_require' suffix for + that requirement. + """ + return ':' + str(req.marker) if req.marker else '' + + def _move_install_requirements_markers(self): + """ + Move requirements in `install_requires` that are using environment + markers `extras_require`. + """ + + # divide the install_requires into two sets, simple ones still + # handled by install_requires and more complex ones handled + # by extras_require. + + def is_simple_req(req): + return not req.marker + + spec_inst_reqs = getattr(self, 'install_requires', None) or () + inst_reqs = list(pkg_resources.parse_requirements(spec_inst_reqs)) + simple_reqs = filter(is_simple_req, inst_reqs) + complex_reqs = filterfalse(is_simple_req, inst_reqs) + self.install_requires = list(map(str, simple_reqs)) + + for r in complex_reqs: + self._tmp_extras_require[':' + str(r.marker)].append(r) + self.extras_require = dict( + (k, [str(r) for r in map(self._clean_req, v)]) + for k, v in self._tmp_extras_require.items() + ) + + def _clean_req(self, req): + """ + Given a Requirement, remove environment markers and return it. + """ + req.marker = None + return req + + def _parse_config_files(self, filenames=None): + """ + Adapted from distutils.dist.Distribution.parse_config_files, + this method provides the same functionality in subtly-improved + ways. + """ + from setuptools.extern.six.moves.configparser import ConfigParser + + # Ignore install directory options if we have a venv + if six.PY3 and sys.prefix != sys.base_prefix: + ignore_options = [ + 'install-base', 'install-platbase', 'install-lib', + 'install-platlib', 'install-purelib', 'install-headers', + 'install-scripts', 'install-data', 'prefix', 'exec-prefix', + 'home', 'user', 'root'] + else: + ignore_options = [] + + ignore_options = frozenset(ignore_options) + + if filenames is None: + filenames = self.find_config_files() + + if DEBUG: + self.announce("Distribution.parse_config_files():") + + parser = ConfigParser() + for filename in filenames: + with io.open(filename, encoding='utf-8') as reader: + if DEBUG: + self.announce(" reading {filename}".format(**locals())) + (parser.read_file if six.PY3 else parser.readfp)(reader) + for section in parser.sections(): + options = parser.options(section) + opt_dict = self.get_option_dict(section) + + for opt in options: + if opt != '__name__' and opt not in ignore_options: + val = self._try_str(parser.get(section, opt)) + opt = opt.replace('-', '_') + opt_dict[opt] = (filename, val) + + # Make the ConfigParser forget everything (so we retain + # the original filenames that options come from) + parser.__init__() + + # If there was a "global" section in the config file, use it + # to set Distribution options. + + if 'global' in self.command_options: + for (opt, (src, val)) in self.command_options['global'].items(): + alias = self.negative_opt.get(opt) + try: + if alias: + setattr(self, alias, not strtobool(val)) + elif opt in ('verbose', 'dry_run'): # ugh! + setattr(self, opt, strtobool(val)) + else: + setattr(self, opt, val) + except ValueError as msg: + raise DistutilsOptionError(msg) + + @staticmethod + def _try_str(val): + """ + On Python 2, much of distutils relies on string values being of + type 'str' (bytes) and not unicode text. If the value can be safely + encoded to bytes using the default encoding, prefer that. + + Why the default encoding? Because that value can be implicitly + decoded back to text if needed. + + Ref #1653 + """ + if six.PY3: + return val + try: + return val.encode() + except UnicodeEncodeError: + pass + return val + + def _set_command_options(self, command_obj, option_dict=None): + """ + Set the options for 'command_obj' from 'option_dict'. Basically + this means copying elements of a dictionary ('option_dict') to + attributes of an instance ('command'). + + 'command_obj' must be a Command instance. If 'option_dict' is not + supplied, uses the standard option dictionary for this command + (from 'self.command_options'). + + (Adopted from distutils.dist.Distribution._set_command_options) + """ + command_name = command_obj.get_command_name() + if option_dict is None: + option_dict = self.get_option_dict(command_name) + + if DEBUG: + self.announce(" setting options for '%s' command:" % command_name) + for (option, (source, value)) in option_dict.items(): + if DEBUG: + self.announce(" %s = %s (from %s)" % (option, value, + source)) + try: + bool_opts = [translate_longopt(o) + for o in command_obj.boolean_options] + except AttributeError: + bool_opts = [] + try: + neg_opt = command_obj.negative_opt + except AttributeError: + neg_opt = {} + + try: + is_string = isinstance(value, six.string_types) + if option in neg_opt and is_string: + setattr(command_obj, neg_opt[option], not strtobool(value)) + elif option in bool_opts and is_string: + setattr(command_obj, option, strtobool(value)) + elif hasattr(command_obj, option): + setattr(command_obj, option, value) + else: + raise DistutilsOptionError( + "error in %s: command '%s' has no such option '%s'" + % (source, command_name, option)) + except ValueError as msg: + raise DistutilsOptionError(msg) + + def parse_config_files(self, filenames=None, ignore_option_errors=False): + """Parses configuration files from various levels + and loads configuration. + + """ + self._parse_config_files(filenames=filenames) + + parse_configuration(self, self.command_options, + ignore_option_errors=ignore_option_errors) + self._finalize_requires() + + def parse_command_line(self): + """Process features after parsing command line options""" + result = _Distribution.parse_command_line(self) + if self.features: + self._finalize_features() + return result + + def _feature_attrname(self, name): + """Convert feature name to corresponding option attribute name""" + return 'with_' + name.replace('-', '_') + + def fetch_build_eggs(self, requires): + """Resolve pre-setup requirements""" + resolved_dists = pkg_resources.working_set.resolve( + pkg_resources.parse_requirements(requires), + installer=self.fetch_build_egg, + replace_conflicting=True, + ) + for dist in resolved_dists: + pkg_resources.working_set.add(dist, replace=True) + return resolved_dists + + def finalize_options(self): + """ + Allow plugins to apply arbitrary operations to the + distribution. Each hook may optionally define a 'order' + to influence the order of execution. Smaller numbers + go first and the default is 0. + """ + hook_key = 'setuptools.finalize_distribution_options' + + def by_order(hook): + return getattr(hook, 'order', 0) + eps = pkg_resources.iter_entry_points(hook_key) + for ep in sorted(eps, key=by_order): + ep.load()(self) + + def _finalize_setup_keywords(self): + for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): + value = getattr(self, ep.name, None) + if value is not None: + ep.require(installer=self.fetch_build_egg) + ep.load()(self, ep.name, value) + + def _finalize_2to3_doctests(self): + if getattr(self, 'convert_2to3_doctests', None): + # XXX may convert to set here when we can rely on set being builtin + self.convert_2to3_doctests = [ + os.path.abspath(p) + for p in self.convert_2to3_doctests + ] + else: + self.convert_2to3_doctests = [] + + def get_egg_cache_dir(self): + egg_cache_dir = os.path.join(os.curdir, '.eggs') + if not os.path.exists(egg_cache_dir): + os.mkdir(egg_cache_dir) + windows_support.hide_file(egg_cache_dir) + readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt') + with open(readme_txt_filename, 'w') as f: + f.write('This directory contains eggs that were downloaded ' + 'by setuptools to build, test, and run plug-ins.\n\n') + f.write('This directory caches those eggs to prevent ' + 'repeated downloads.\n\n') + f.write('However, it is safe to delete this directory.\n\n') + + return egg_cache_dir + + def fetch_build_egg(self, req): + """Fetch an egg needed for building""" + from setuptools.installer import fetch_build_egg + return fetch_build_egg(self, req) + + def _finalize_feature_opts(self): + """Add --with-X/--without-X options based on optional features""" + + if not self.features: + return + + go = [] + no = self.negative_opt.copy() + + for name, feature in self.features.items(): + self._set_feature(name, None) + feature.validate(self) + + if feature.optional: + descr = feature.description + incdef = ' (default)' + excdef = '' + if not feature.include_by_default(): + excdef, incdef = incdef, excdef + + new = ( + ('with-' + name, None, 'include ' + descr + incdef), + ('without-' + name, None, 'exclude ' + descr + excdef), + ) + go.extend(new) + no['without-' + name] = 'with-' + name + + self.global_options = self.feature_options = go + self.global_options + self.negative_opt = self.feature_negopt = no + + def _finalize_features(self): + """Add/remove features and resolve dependencies between them""" + + # First, flag all the enabled items (and thus their dependencies) + for name, feature in self.features.items(): + enabled = self.feature_is_included(name) + if enabled or (enabled is None and feature.include_by_default()): + feature.include_in(self) + self._set_feature(name, 1) + + # Then disable the rest, so that off-by-default features don't + # get flagged as errors when they're required by an enabled feature + for name, feature in self.features.items(): + if not self.feature_is_included(name): + feature.exclude_from(self) + self._set_feature(name, 0) + + def get_command_class(self, command): + """Pluggable version of get_command_class()""" + if command in self.cmdclass: + return self.cmdclass[command] + + eps = pkg_resources.iter_entry_points('distutils.commands', command) + for ep in eps: + ep.require(installer=self.fetch_build_egg) + self.cmdclass[command] = cmdclass = ep.load() + return cmdclass + else: + return _Distribution.get_command_class(self, command) + + def print_commands(self): + for ep in pkg_resources.iter_entry_points('distutils.commands'): + if ep.name not in self.cmdclass: + # don't require extras as the commands won't be invoked + cmdclass = ep.resolve() + self.cmdclass[ep.name] = cmdclass + return _Distribution.print_commands(self) + + def get_command_list(self): + for ep in pkg_resources.iter_entry_points('distutils.commands'): + if ep.name not in self.cmdclass: + # don't require extras as the commands won't be invoked + cmdclass = ep.resolve() + self.cmdclass[ep.name] = cmdclass + return _Distribution.get_command_list(self) + + def _set_feature(self, name, status): + """Set feature's inclusion status""" + setattr(self, self._feature_attrname(name), status) + + def feature_is_included(self, name): + """Return 1 if feature is included, 0 if excluded, 'None' if unknown""" + return getattr(self, self._feature_attrname(name)) + + def include_feature(self, name): + """Request inclusion of feature named 'name'""" + + if self.feature_is_included(name) == 0: + descr = self.features[name].description + raise DistutilsOptionError( + descr + " is required, but was excluded or is not available" + ) + self.features[name].include_in(self) + self._set_feature(name, 1) + + def include(self, **attrs): + """Add items to distribution that are named in keyword arguments + + For example, 'dist.include(py_modules=["x"])' would add 'x' to + the distribution's 'py_modules' attribute, if it was not already + there. + + Currently, this method only supports inclusion for attributes that are + lists or tuples. If you need to add support for adding to other + attributes in this or a subclass, you can add an '_include_X' method, + where 'X' is the name of the attribute. The method will be called with + the value passed to 'include()'. So, 'dist.include(foo={"bar":"baz"})' + will try to call 'dist._include_foo({"bar":"baz"})', which can then + handle whatever special inclusion logic is needed. + """ + for k, v in attrs.items(): + include = getattr(self, '_include_' + k, None) + if include: + include(v) + else: + self._include_misc(k, v) + + def exclude_package(self, package): + """Remove packages, modules, and extensions in named package""" + + pfx = package + '.' + if self.packages: + self.packages = [ + p for p in self.packages + if p != package and not p.startswith(pfx) + ] + + if self.py_modules: + self.py_modules = [ + p for p in self.py_modules + if p != package and not p.startswith(pfx) + ] + + if self.ext_modules: + self.ext_modules = [ + p for p in self.ext_modules + if p.name != package and not p.name.startswith(pfx) + ] + + def has_contents_for(self, package): + """Return true if 'exclude_package(package)' would do something""" + + pfx = package + '.' + + for p in self.iter_distribution_names(): + if p == package or p.startswith(pfx): + return True + + def _exclude_misc(self, name, value): + """Handle 'exclude()' for list/tuple attrs without a special handler""" + if not isinstance(value, sequence): + raise DistutilsSetupError( + "%s: setting must be a list or tuple (%r)" % (name, value) + ) + try: + old = getattr(self, name) + except AttributeError: + raise DistutilsSetupError( + "%s: No such distribution setting" % name + ) + if old is not None and not isinstance(old, sequence): + raise DistutilsSetupError( + name + ": this setting cannot be changed via include/exclude" + ) + elif old: + setattr(self, name, [item for item in old if item not in value]) + + def _include_misc(self, name, value): + """Handle 'include()' for list/tuple attrs without a special handler""" + + if not isinstance(value, sequence): + raise DistutilsSetupError( + "%s: setting must be a list (%r)" % (name, value) + ) + try: + old = getattr(self, name) + except AttributeError: + raise DistutilsSetupError( + "%s: No such distribution setting" % name + ) + if old is None: + setattr(self, name, value) + elif not isinstance(old, sequence): + raise DistutilsSetupError( + name + ": this setting cannot be changed via include/exclude" + ) + else: + new = [item for item in value if item not in old] + setattr(self, name, old + new) + + def exclude(self, **attrs): + """Remove items from distribution that are named in keyword arguments + + For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from + the distribution's 'py_modules' attribute. Excluding packages uses + the 'exclude_package()' method, so all of the package's contained + packages, modules, and extensions are also excluded. + + Currently, this method only supports exclusion from attributes that are + lists or tuples. If you need to add support for excluding from other + attributes in this or a subclass, you can add an '_exclude_X' method, + where 'X' is the name of the attribute. The method will be called with + the value passed to 'exclude()'. So, 'dist.exclude(foo={"bar":"baz"})' + will try to call 'dist._exclude_foo({"bar":"baz"})', which can then + handle whatever special exclusion logic is needed. + """ + for k, v in attrs.items(): + exclude = getattr(self, '_exclude_' + k, None) + if exclude: + exclude(v) + else: + self._exclude_misc(k, v) + + def _exclude_packages(self, packages): + if not isinstance(packages, sequence): + raise DistutilsSetupError( + "packages: setting must be a list or tuple (%r)" % (packages,) + ) + list(map(self.exclude_package, packages)) + + def _parse_command_opts(self, parser, args): + # Remove --with-X/--without-X options when processing command args + self.global_options = self.__class__.global_options + self.negative_opt = self.__class__.negative_opt + + # First, expand any aliases + command = args[0] + aliases = self.get_option_dict('aliases') + while command in aliases: + src, alias = aliases[command] + del aliases[command] # ensure each alias can expand only once! + import shlex + args[:1] = shlex.split(alias, True) + command = args[0] + + nargs = _Distribution._parse_command_opts(self, parser, args) + + # Handle commands that want to consume all remaining arguments + cmd_class = self.get_command_class(command) + if getattr(cmd_class, 'command_consumes_arguments', None): + self.get_option_dict(command)['args'] = ("command line", nargs) + if nargs is not None: + return [] + + return nargs + + def get_cmdline_options(self): + """Return a '{cmd: {opt:val}}' map of all command-line options + + Option names are all long, but do not include the leading '--', and + contain dashes rather than underscores. If the option doesn't take + an argument (e.g. '--quiet'), the 'val' is 'None'. + + Note that options provided by config files are intentionally excluded. + """ + + d = {} + + for cmd, opts in self.command_options.items(): + + for opt, (src, val) in opts.items(): + + if src != "command line": + continue + + opt = opt.replace('_', '-') + + if val == 0: + cmdobj = self.get_command_obj(cmd) + neg_opt = self.negative_opt.copy() + neg_opt.update(getattr(cmdobj, 'negative_opt', {})) + for neg, pos in neg_opt.items(): + if pos == opt: + opt = neg + val = None + break + else: + raise AssertionError("Shouldn't be able to get here") + + elif val == 1: + val = None + + d.setdefault(cmd, {})[opt] = val + + return d + + def iter_distribution_names(self): + """Yield all packages, modules, and extension names in distribution""" + + for pkg in self.packages or (): + yield pkg + + for module in self.py_modules or (): + yield module + + for ext in self.ext_modules or (): + if isinstance(ext, tuple): + name, buildinfo = ext + else: + name = ext.name + if name.endswith('module'): + name = name[:-6] + yield name + + def handle_display_options(self, option_order): + """If there were any non-global "display-only" options + (--help-commands or the metadata display options) on the command + line, display the requested info and return true; else return + false. + """ + import sys + + if six.PY2 or self.help_commands: + return _Distribution.handle_display_options(self, option_order) + + # Stdout may be StringIO (e.g. in tests) + if not isinstance(sys.stdout, io.TextIOWrapper): + return _Distribution.handle_display_options(self, option_order) + + # Don't wrap stdout if utf-8 is already the encoding. Provides + # workaround for #334. + if sys.stdout.encoding.lower() in ('utf-8', 'utf8'): + return _Distribution.handle_display_options(self, option_order) + + # Print metadata in UTF-8 no matter the platform + encoding = sys.stdout.encoding + errors = sys.stdout.errors + newline = sys.platform != 'win32' and '\n' or None + line_buffering = sys.stdout.line_buffering + + sys.stdout = io.TextIOWrapper( + sys.stdout.detach(), 'utf-8', errors, newline, line_buffering) + try: + return _Distribution.handle_display_options(self, option_order) + finally: + sys.stdout = io.TextIOWrapper( + sys.stdout.detach(), encoding, errors, newline, line_buffering) + + +class Feature: + """ + **deprecated** -- The `Feature` facility was never completely implemented + or supported, `has reported issues + `_ and will be removed in + a future version. + + A subset of the distribution that can be excluded if unneeded/wanted + + Features are created using these keyword arguments: + + 'description' -- a short, human readable description of the feature, to + be used in error messages, and option help messages. + + 'standard' -- if true, the feature is included by default if it is + available on the current system. Otherwise, the feature is only + included if requested via a command line '--with-X' option, or if + another included feature requires it. The default setting is 'False'. + + 'available' -- if true, the feature is available for installation on the + current system. The default setting is 'True'. + + 'optional' -- if true, the feature's inclusion can be controlled from the + command line, using the '--with-X' or '--without-X' options. If + false, the feature's inclusion status is determined automatically, + based on 'availabile', 'standard', and whether any other feature + requires it. The default setting is 'True'. + + 'require_features' -- a string or sequence of strings naming features + that should also be included if this feature is included. Defaults to + empty list. May also contain 'Require' objects that should be + added/removed from the distribution. + + 'remove' -- a string or list of strings naming packages to be removed + from the distribution if this feature is *not* included. If the + feature *is* included, this argument is ignored. This argument exists + to support removing features that "crosscut" a distribution, such as + defining a 'tests' feature that removes all the 'tests' subpackages + provided by other features. The default for this argument is an empty + list. (Note: the named package(s) or modules must exist in the base + distribution when the 'setup()' function is initially called.) + + other keywords -- any other keyword arguments are saved, and passed to + the distribution's 'include()' and 'exclude()' methods when the + feature is included or excluded, respectively. So, for example, you + could pass 'packages=["a","b"]' to cause packages 'a' and 'b' to be + added or removed from the distribution as appropriate. + + A feature must include at least one 'requires', 'remove', or other + keyword argument. Otherwise, it can't affect the distribution in any way. + Note also that you can subclass 'Feature' to create your own specialized + feature types that modify the distribution in other ways when included or + excluded. See the docstrings for the various methods here for more detail. + Aside from the methods, the only feature attributes that distributions look + at are 'description' and 'optional'. + """ + + @staticmethod + def warn_deprecated(): + msg = ( + "Features are deprecated and will be removed in a future " + "version. See https://github.com/pypa/setuptools/issues/65." + ) + warnings.warn(msg, DistDeprecationWarning, stacklevel=3) + + def __init__( + self, description, standard=False, available=True, + optional=True, require_features=(), remove=(), **extras): + self.warn_deprecated() + + self.description = description + self.standard = standard + self.available = available + self.optional = optional + if isinstance(require_features, (str, Require)): + require_features = require_features, + + self.require_features = [ + r for r in require_features if isinstance(r, str) + ] + er = [r for r in require_features if not isinstance(r, str)] + if er: + extras['require_features'] = er + + if isinstance(remove, str): + remove = remove, + self.remove = remove + self.extras = extras + + if not remove and not require_features and not extras: + raise DistutilsSetupError( + "Feature %s: must define 'require_features', 'remove', or " + "at least one of 'packages', 'py_modules', etc." + ) + + def include_by_default(self): + """Should this feature be included by default?""" + return self.available and self.standard + + def include_in(self, dist): + """Ensure feature and its requirements are included in distribution + + You may override this in a subclass to perform additional operations on + the distribution. Note that this method may be called more than once + per feature, and so should be idempotent. + + """ + + if not self.available: + raise DistutilsPlatformError( + self.description + " is required, " + "but is not available on this platform" + ) + + dist.include(**self.extras) + + for f in self.require_features: + dist.include_feature(f) + + def exclude_from(self, dist): + """Ensure feature is excluded from distribution + + You may override this in a subclass to perform additional operations on + the distribution. This method will be called at most once per + feature, and only after all included features have been asked to + include themselves. + """ + + dist.exclude(**self.extras) + + if self.remove: + for item in self.remove: + dist.exclude_package(item) + + def validate(self, dist): + """Verify that feature makes sense in context of distribution + + This method is called by the distribution just before it parses its + command line. It checks to ensure that the 'remove' attribute, if any, + contains only valid package/module names that are present in the base + distribution when 'setup()' is called. You may override it in a + subclass to perform any other required validation of the feature + against a target distribution. + """ + + for item in self.remove: + if not dist.has_contents_for(item): + raise DistutilsSetupError( + "%s wants to be able to remove %s, but the distribution" + " doesn't contain any packages or modules under %s" + % (self.description, item, item) + ) + + +class DistDeprecationWarning(SetuptoolsDeprecationWarning): + """Class for warning about deprecations in dist in + setuptools. Not ignored by default, unlike DeprecationWarning.""" diff --git a/robot/lib/python3.8/site-packages/setuptools/errors.py b/robot/lib/python3.8/site-packages/setuptools/errors.py new file mode 100644 index 0000000000000000000000000000000000000000..2701747f56cc77845159f2c5fee2d0ce114259af --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/errors.py @@ -0,0 +1,16 @@ +"""setuptools.errors + +Provides exceptions used by setuptools modules. +""" + +from distutils.errors import DistutilsError + + +class RemovedCommandError(DistutilsError, RuntimeError): + """Error used for commands that have been removed in setuptools. + + Since ``setuptools`` is built on ``distutils``, simply removing a command + from ``setuptools`` will make the behavior fall back to ``distutils``; this + error is raised if a command exists in ``distutils`` but has been actively + removed in ``setuptools``. + """ diff --git a/robot/lib/python3.8/site-packages/setuptools/extension.py b/robot/lib/python3.8/site-packages/setuptools/extension.py new file mode 100644 index 0000000000000000000000000000000000000000..29468894f828128f4c36660167dd1f9e68e584be --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/extension.py @@ -0,0 +1,57 @@ +import re +import functools +import distutils.core +import distutils.errors +import distutils.extension + +from setuptools.extern.six.moves import map + +from .monkey import get_unpatched + + +def _have_cython(): + """ + Return True if Cython can be imported. + """ + cython_impl = 'Cython.Distutils.build_ext' + try: + # from (cython_impl) import build_ext + __import__(cython_impl, fromlist=['build_ext']).build_ext + return True + except Exception: + pass + return False + + +# for compatibility +have_pyrex = _have_cython + +_Extension = get_unpatched(distutils.core.Extension) + + +class Extension(_Extension): + """Extension that uses '.c' files in place of '.pyx' files""" + + def __init__(self, name, sources, *args, **kw): + # The *args is needed for compatibility as calls may use positional + # arguments. py_limited_api may be set only via keyword. + self.py_limited_api = kw.pop("py_limited_api", False) + _Extension.__init__(self, name, sources, *args, **kw) + + def _convert_pyx_sources_to_lang(self): + """ + Replace sources with .pyx extensions to sources with the target + language extension. This mechanism allows language authors to supply + pre-converted sources but to prefer the .pyx sources. + """ + if _have_cython(): + # the build has Cython, so allow it to compile the .pyx files + return + lang = self.language or '' + target_ext = '.cpp' if lang.lower() == 'c++' else '.c' + sub = functools.partial(re.sub, '.pyx$', target_ext) + self.sources = list(map(sub, self.sources)) + + +class Library(Extension): + """Just like a regular Extension, but built as a library instead""" diff --git a/robot/lib/python3.8/site-packages/setuptools/extern/__init__.py b/robot/lib/python3.8/site-packages/setuptools/extern/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e8c616f910bb9bb874c3d44f1efe5239ecb8f621 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/extern/__init__.py @@ -0,0 +1,73 @@ +import sys + + +class VendorImporter: + """ + A PEP 302 meta path importer for finding optionally-vendored + or otherwise naturally-installed packages from root_name. + """ + + def __init__(self, root_name, vendored_names=(), vendor_pkg=None): + self.root_name = root_name + self.vendored_names = set(vendored_names) + self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor') + + @property + def search_path(self): + """ + Search first the vendor package then as a natural package. + """ + yield self.vendor_pkg + '.' + yield '' + + def find_module(self, fullname, path=None): + """ + Return self when fullname starts with root_name and the + target module is one vendored through this importer. + """ + root, base, target = fullname.partition(self.root_name + '.') + if root: + return + if not any(map(target.startswith, self.vendored_names)): + return + return self + + def load_module(self, fullname): + """ + Iterate over the search path to locate and load fullname. + """ + root, base, target = fullname.partition(self.root_name + '.') + for prefix in self.search_path: + try: + extant = prefix + target + __import__(extant) + mod = sys.modules[extant] + sys.modules[fullname] = mod + # mysterious hack: + # Remove the reference to the extant package/module + # on later Python versions to cause relative imports + # in the vendor package to resolve the same modules + # as those going through this importer. + if sys.version_info >= (3, ): + del sys.modules[extant] + return mod + except ImportError: + pass + else: + raise ImportError( + "The '{target}' package is required; " + "normally this is bundled with this package so if you get " + "this warning, consult the packager of your " + "distribution.".format(**locals()) + ) + + def install(self): + """ + Install this importer into sys.meta_path if not already present. + """ + if self not in sys.meta_path: + sys.meta_path.append(self) + + +names = 'six', 'packaging', 'pyparsing', 'ordered_set', +VendorImporter(__name__, names, 'setuptools._vendor').install() diff --git a/robot/lib/python3.8/site-packages/setuptools/glob.py b/robot/lib/python3.8/site-packages/setuptools/glob.py new file mode 100644 index 0000000000000000000000000000000000000000..9d7cbc5da68da8605d271b9314befb206b87bca6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/glob.py @@ -0,0 +1,174 @@ +""" +Filename globbing utility. Mostly a copy of `glob` from Python 3.5. + +Changes include: + * `yield from` and PEP3102 `*` removed. + * Hidden files are not ignored. +""" + +import os +import re +import fnmatch + +__all__ = ["glob", "iglob", "escape"] + + +def glob(pathname, recursive=False): + """Return a list of paths matching a pathname pattern. + + The pattern may contain simple shell-style wildcards a la + fnmatch. However, unlike fnmatch, filenames starting with a + dot are special cases that are not matched by '*' and '?' + patterns. + + If recursive is true, the pattern '**' will match any files and + zero or more directories and subdirectories. + """ + return list(iglob(pathname, recursive=recursive)) + + +def iglob(pathname, recursive=False): + """Return an iterator which yields the paths matching a pathname pattern. + + The pattern may contain simple shell-style wildcards a la + fnmatch. However, unlike fnmatch, filenames starting with a + dot are special cases that are not matched by '*' and '?' + patterns. + + If recursive is true, the pattern '**' will match any files and + zero or more directories and subdirectories. + """ + it = _iglob(pathname, recursive) + if recursive and _isrecursive(pathname): + s = next(it) # skip empty string + assert not s + return it + + +def _iglob(pathname, recursive): + dirname, basename = os.path.split(pathname) + if not has_magic(pathname): + if basename: + if os.path.lexists(pathname): + yield pathname + else: + # Patterns ending with a slash should match only directories + if os.path.isdir(dirname): + yield pathname + return + if not dirname: + if recursive and _isrecursive(basename): + for x in glob2(dirname, basename): + yield x + else: + for x in glob1(dirname, basename): + yield x + return + # `os.path.split()` returns the argument itself as a dirname if it is a + # drive or UNC path. Prevent an infinite recursion if a drive or UNC path + # contains magic characters (i.e. r'\\?\C:'). + if dirname != pathname and has_magic(dirname): + dirs = _iglob(dirname, recursive) + else: + dirs = [dirname] + if has_magic(basename): + if recursive and _isrecursive(basename): + glob_in_dir = glob2 + else: + glob_in_dir = glob1 + else: + glob_in_dir = glob0 + for dirname in dirs: + for name in glob_in_dir(dirname, basename): + yield os.path.join(dirname, name) + + +# These 2 helper functions non-recursively glob inside a literal directory. +# They return a list of basenames. `glob1` accepts a pattern while `glob0` +# takes a literal basename (so it only has to check for its existence). + + +def glob1(dirname, pattern): + if not dirname: + if isinstance(pattern, bytes): + dirname = os.curdir.encode('ASCII') + else: + dirname = os.curdir + try: + names = os.listdir(dirname) + except OSError: + return [] + return fnmatch.filter(names, pattern) + + +def glob0(dirname, basename): + if not basename: + # `os.path.split()` returns an empty basename for paths ending with a + # directory separator. 'q*x/' should match only directories. + if os.path.isdir(dirname): + return [basename] + else: + if os.path.lexists(os.path.join(dirname, basename)): + return [basename] + return [] + + +# This helper function recursively yields relative pathnames inside a literal +# directory. + + +def glob2(dirname, pattern): + assert _isrecursive(pattern) + yield pattern[:0] + for x in _rlistdir(dirname): + yield x + + +# Recursively yields relative pathnames inside a literal directory. +def _rlistdir(dirname): + if not dirname: + if isinstance(dirname, bytes): + dirname = os.curdir.encode('ASCII') + else: + dirname = os.curdir + try: + names = os.listdir(dirname) + except os.error: + return + for x in names: + yield x + path = os.path.join(dirname, x) if dirname else x + for y in _rlistdir(path): + yield os.path.join(x, y) + + +magic_check = re.compile('([*?[])') +magic_check_bytes = re.compile(b'([*?[])') + + +def has_magic(s): + if isinstance(s, bytes): + match = magic_check_bytes.search(s) + else: + match = magic_check.search(s) + return match is not None + + +def _isrecursive(pattern): + if isinstance(pattern, bytes): + return pattern == b'**' + else: + return pattern == '**' + + +def escape(pathname): + """Escape all special characters. + """ + # Escaping is done by wrapping any of "*?[" between square brackets. + # Metacharacters do not work in the drive part and shouldn't be escaped. + drive, pathname = os.path.splitdrive(pathname) + if isinstance(pathname, bytes): + pathname = magic_check_bytes.sub(br'[\1]', pathname) + else: + pathname = magic_check.sub(r'[\1]', pathname) + return drive + pathname diff --git a/robot/lib/python3.8/site-packages/setuptools/gui-32.exe b/robot/lib/python3.8/site-packages/setuptools/gui-32.exe new file mode 100644 index 0000000000000000000000000000000000000000..f8d3509653ba8f80ca7f3aa7f95616142ba83a94 Binary files /dev/null and b/robot/lib/python3.8/site-packages/setuptools/gui-32.exe differ diff --git a/robot/lib/python3.8/site-packages/setuptools/gui-64.exe b/robot/lib/python3.8/site-packages/setuptools/gui-64.exe new file mode 100644 index 0000000000000000000000000000000000000000..330c51a5dde15a0bb610a48cd0ca11770c914dae Binary files /dev/null and b/robot/lib/python3.8/site-packages/setuptools/gui-64.exe differ diff --git a/robot/lib/python3.8/site-packages/setuptools/gui.exe b/robot/lib/python3.8/site-packages/setuptools/gui.exe new file mode 100644 index 0000000000000000000000000000000000000000..f8d3509653ba8f80ca7f3aa7f95616142ba83a94 Binary files /dev/null and b/robot/lib/python3.8/site-packages/setuptools/gui.exe differ diff --git a/robot/lib/python3.8/site-packages/setuptools/installer.py b/robot/lib/python3.8/site-packages/setuptools/installer.py new file mode 100644 index 0000000000000000000000000000000000000000..9f8be2ef8427651e3b0fbef497535e152dde66b1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/installer.py @@ -0,0 +1,150 @@ +import glob +import os +import subprocess +import sys +from distutils import log +from distutils.errors import DistutilsError + +import pkg_resources +from setuptools.command.easy_install import easy_install +from setuptools.extern import six +from setuptools.wheel import Wheel + +from .py31compat import TemporaryDirectory + + +def _fixup_find_links(find_links): + """Ensure find-links option end-up being a list of strings.""" + if isinstance(find_links, six.string_types): + return find_links.split() + assert isinstance(find_links, (tuple, list)) + return find_links + + +def _legacy_fetch_build_egg(dist, req): + """Fetch an egg needed for building. + + Legacy path using EasyInstall. + """ + tmp_dist = dist.__class__({'script_args': ['easy_install']}) + opts = tmp_dist.get_option_dict('easy_install') + opts.clear() + opts.update( + (k, v) + for k, v in dist.get_option_dict('easy_install').items() + if k in ( + # don't use any other settings + 'find_links', 'site_dirs', 'index_url', + 'optimize', 'site_dirs', 'allow_hosts', + )) + if dist.dependency_links: + links = dist.dependency_links[:] + if 'find_links' in opts: + links = _fixup_find_links(opts['find_links'][1]) + links + opts['find_links'] = ('setup', links) + install_dir = dist.get_egg_cache_dir() + cmd = easy_install( + tmp_dist, args=["x"], install_dir=install_dir, + exclude_scripts=True, + always_copy=False, build_directory=None, editable=False, + upgrade=False, multi_version=True, no_report=True, user=False + ) + cmd.ensure_finalized() + return cmd.easy_install(req) + + +def fetch_build_egg(dist, req): + """Fetch an egg needed for building. + + Use pip/wheel to fetch/build a wheel.""" + # Check pip is available. + try: + pkg_resources.get_distribution('pip') + except pkg_resources.DistributionNotFound: + dist.announce( + 'WARNING: The pip package is not available, falling back ' + 'to EasyInstall for handling setup_requires/test_requires; ' + 'this is deprecated and will be removed in a future version.' + , log.WARN + ) + return _legacy_fetch_build_egg(dist, req) + # Warn if wheel is not. + try: + pkg_resources.get_distribution('wheel') + except pkg_resources.DistributionNotFound: + dist.announce('WARNING: The wheel package is not available.', log.WARN) + # Ignore environment markers; if supplied, it is required. + req = strip_marker(req) + # Take easy_install options into account, but do not override relevant + # pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll + # take precedence. + opts = dist.get_option_dict('easy_install') + if 'allow_hosts' in opts: + raise DistutilsError('the `allow-hosts` option is not supported ' + 'when using pip to install requirements.') + if 'PIP_QUIET' in os.environ or 'PIP_VERBOSE' in os.environ: + quiet = False + else: + quiet = True + if 'PIP_INDEX_URL' in os.environ: + index_url = None + elif 'index_url' in opts: + index_url = opts['index_url'][1] + else: + index_url = None + if 'find_links' in opts: + find_links = _fixup_find_links(opts['find_links'][1])[:] + else: + find_links = [] + if dist.dependency_links: + find_links.extend(dist.dependency_links) + eggs_dir = os.path.realpath(dist.get_egg_cache_dir()) + environment = pkg_resources.Environment() + for egg_dist in pkg_resources.find_distributions(eggs_dir): + if egg_dist in req and environment.can_add(egg_dist): + return egg_dist + with TemporaryDirectory() as tmpdir: + cmd = [ + sys.executable, '-m', 'pip', + '--disable-pip-version-check', + 'wheel', '--no-deps', + '-w', tmpdir, + ] + if quiet: + cmd.append('--quiet') + if index_url is not None: + cmd.extend(('--index-url', index_url)) + if find_links is not None: + for link in find_links: + cmd.extend(('--find-links', link)) + # If requirement is a PEP 508 direct URL, directly pass + # the URL to pip, as `req @ url` does not work on the + # command line. + if req.url: + cmd.append(req.url) + else: + cmd.append(str(req)) + try: + subprocess.check_call(cmd) + except subprocess.CalledProcessError as e: + raise DistutilsError(str(e)) + wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0]) + dist_location = os.path.join(eggs_dir, wheel.egg_name()) + wheel.install_as_egg(dist_location) + dist_metadata = pkg_resources.PathMetadata( + dist_location, os.path.join(dist_location, 'EGG-INFO')) + dist = pkg_resources.Distribution.from_filename( + dist_location, metadata=dist_metadata) + return dist + + +def strip_marker(req): + """ + Return a new requirement without the environment marker to avoid + calling pip with something like `babel; extra == "i18n"`, which + would always be ignored. + """ + # create a copy to avoid mutating the input + req = pkg_resources.Requirement.parse(str(req)) + req.marker = None + return req diff --git a/robot/lib/python3.8/site-packages/setuptools/launch.py b/robot/lib/python3.8/site-packages/setuptools/launch.py new file mode 100644 index 0000000000000000000000000000000000000000..308283ea939ed9bced7b099eb8a1879aa9c203d4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/launch.py @@ -0,0 +1,35 @@ +""" +Launch the Python script on the command line after +setuptools is bootstrapped via import. +""" + +# Note that setuptools gets imported implicitly by the +# invocation of this script using python -m setuptools.launch + +import tokenize +import sys + + +def run(): + """ + Run the script in sys.argv[1] as if it had + been invoked naturally. + """ + __builtins__ + script_name = sys.argv[1] + namespace = dict( + __file__=script_name, + __name__='__main__', + __doc__=None, + ) + sys.argv[:] = sys.argv[1:] + + open_ = getattr(tokenize, 'open', open) + script = open_(script_name).read() + norm_script = script.replace('\\r\\n', '\\n') + code = compile(norm_script, script_name, 'exec') + exec(code, namespace) + + +if __name__ == '__main__': + run() diff --git a/robot/lib/python3.8/site-packages/setuptools/lib2to3_ex.py b/robot/lib/python3.8/site-packages/setuptools/lib2to3_ex.py new file mode 100644 index 0000000000000000000000000000000000000000..4b1a73feb26fdad65bafdeb21f5ce6abfb905fc0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/lib2to3_ex.py @@ -0,0 +1,62 @@ +""" +Customized Mixin2to3 support: + + - adds support for converting doctests + + +This module raises an ImportError on Python 2. +""" + +from distutils.util import Mixin2to3 as _Mixin2to3 +from distutils import log +from lib2to3.refactor import RefactoringTool, get_fixers_from_package + +import setuptools + + +class DistutilsRefactoringTool(RefactoringTool): + def log_error(self, msg, *args, **kw): + log.error(msg, *args) + + def log_message(self, msg, *args): + log.info(msg, *args) + + def log_debug(self, msg, *args): + log.debug(msg, *args) + + +class Mixin2to3(_Mixin2to3): + def run_2to3(self, files, doctests=False): + # See of the distribution option has been set, otherwise check the + # setuptools default. + if self.distribution.use_2to3 is not True: + return + if not files: + return + log.info("Fixing " + " ".join(files)) + self.__build_fixer_names() + self.__exclude_fixers() + if doctests: + if setuptools.run_2to3_on_doctests: + r = DistutilsRefactoringTool(self.fixer_names) + r.refactor(files, write=True, doctests_only=True) + else: + _Mixin2to3.run_2to3(self, files) + + def __build_fixer_names(self): + if self.fixer_names: + return + self.fixer_names = [] + for p in setuptools.lib2to3_fixer_packages: + self.fixer_names.extend(get_fixers_from_package(p)) + if self.distribution.use_2to3_fixers is not None: + for p in self.distribution.use_2to3_fixers: + self.fixer_names.extend(get_fixers_from_package(p)) + + def __exclude_fixers(self): + excluded_fixers = getattr(self, 'exclude_fixers', []) + if self.distribution.use_2to3_exclude_fixers is not None: + excluded_fixers.extend(self.distribution.use_2to3_exclude_fixers) + for fixer_name in excluded_fixers: + if fixer_name in self.fixer_names: + self.fixer_names.remove(fixer_name) diff --git a/robot/lib/python3.8/site-packages/setuptools/monkey.py b/robot/lib/python3.8/site-packages/setuptools/monkey.py new file mode 100644 index 0000000000000000000000000000000000000000..3c77f8cf27f0ab1e71d64cfc114ef9d1bf72295c --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/monkey.py @@ -0,0 +1,179 @@ +""" +Monkey patching of distutils. +""" + +import sys +import distutils.filelist +import platform +import types +import functools +from importlib import import_module +import inspect + +from setuptools.extern import six + +import setuptools + +__all__ = [] +""" +Everything is private. Contact the project team +if you think you need this functionality. +""" + + +def _get_mro(cls): + """ + Returns the bases classes for cls sorted by the MRO. + + Works around an issue on Jython where inspect.getmro will not return all + base classes if multiple classes share the same name. Instead, this + function will return a tuple containing the class itself, and the contents + of cls.__bases__. See https://github.com/pypa/setuptools/issues/1024. + """ + if platform.python_implementation() == "Jython": + return (cls,) + cls.__bases__ + return inspect.getmro(cls) + + +def get_unpatched(item): + lookup = ( + get_unpatched_class if isinstance(item, six.class_types) else + get_unpatched_function if isinstance(item, types.FunctionType) else + lambda item: None + ) + return lookup(item) + + +def get_unpatched_class(cls): + """Protect against re-patching the distutils if reloaded + + Also ensures that no other distutils extension monkeypatched the distutils + first. + """ + external_bases = ( + cls + for cls in _get_mro(cls) + if not cls.__module__.startswith('setuptools') + ) + base = next(external_bases) + if not base.__module__.startswith('distutils'): + msg = "distutils has already been patched by %r" % cls + raise AssertionError(msg) + return base + + +def patch_all(): + # we can't patch distutils.cmd, alas + distutils.core.Command = setuptools.Command + + has_issue_12885 = sys.version_info <= (3, 5, 3) + + if has_issue_12885: + # fix findall bug in distutils (http://bugs.python.org/issue12885) + distutils.filelist.findall = setuptools.findall + + needs_warehouse = ( + sys.version_info < (2, 7, 13) + or + (3, 4) < sys.version_info < (3, 4, 6) + or + (3, 5) < sys.version_info <= (3, 5, 3) + ) + + if needs_warehouse: + warehouse = 'https://upload.pypi.org/legacy/' + distutils.config.PyPIRCCommand.DEFAULT_REPOSITORY = warehouse + + _patch_distribution_metadata() + + # Install Distribution throughout the distutils + for module in distutils.dist, distutils.core, distutils.cmd: + module.Distribution = setuptools.dist.Distribution + + # Install the patched Extension + distutils.core.Extension = setuptools.extension.Extension + distutils.extension.Extension = setuptools.extension.Extension + if 'distutils.command.build_ext' in sys.modules: + sys.modules['distutils.command.build_ext'].Extension = ( + setuptools.extension.Extension + ) + + patch_for_msvc_specialized_compiler() + + +def _patch_distribution_metadata(): + """Patch write_pkg_file and read_pkg_file for higher metadata standards""" + for attr in ('write_pkg_file', 'read_pkg_file', 'get_metadata_version'): + new_val = getattr(setuptools.dist, attr) + setattr(distutils.dist.DistributionMetadata, attr, new_val) + + +def patch_func(replacement, target_mod, func_name): + """ + Patch func_name in target_mod with replacement + + Important - original must be resolved by name to avoid + patching an already patched function. + """ + original = getattr(target_mod, func_name) + + # set the 'unpatched' attribute on the replacement to + # point to the original. + vars(replacement).setdefault('unpatched', original) + + # replace the function in the original module + setattr(target_mod, func_name, replacement) + + +def get_unpatched_function(candidate): + return getattr(candidate, 'unpatched') + + +def patch_for_msvc_specialized_compiler(): + """ + Patch functions in distutils to use standalone Microsoft Visual C++ + compilers. + """ + # import late to avoid circular imports on Python < 3.5 + msvc = import_module('setuptools.msvc') + + if platform.system() != 'Windows': + # Compilers only availables on Microsoft Windows + return + + def patch_params(mod_name, func_name): + """ + Prepare the parameters for patch_func to patch indicated function. + """ + repl_prefix = 'msvc9_' if 'msvc9' in mod_name else 'msvc14_' + repl_name = repl_prefix + func_name.lstrip('_') + repl = getattr(msvc, repl_name) + mod = import_module(mod_name) + if not hasattr(mod, func_name): + raise ImportError(func_name) + return repl, mod, func_name + + # Python 2.7 to 3.4 + msvc9 = functools.partial(patch_params, 'distutils.msvc9compiler') + + # Python 3.5+ + msvc14 = functools.partial(patch_params, 'distutils._msvccompiler') + + try: + # Patch distutils.msvc9compiler + patch_func(*msvc9('find_vcvarsall')) + patch_func(*msvc9('query_vcvarsall')) + except ImportError: + pass + + try: + # Patch distutils._msvccompiler._get_vc_env + patch_func(*msvc14('_get_vc_env')) + except ImportError: + pass + + try: + # Patch distutils._msvccompiler.gen_lib_options for Numpy + patch_func(*msvc14('gen_lib_options')) + except ImportError: + pass diff --git a/robot/lib/python3.8/site-packages/setuptools/msvc.py b/robot/lib/python3.8/site-packages/setuptools/msvc.py new file mode 100644 index 0000000000000000000000000000000000000000..2ffe1c81ee629c98246e9e72bf630431fa7905b6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/msvc.py @@ -0,0 +1,1679 @@ +""" +Improved support for Microsoft Visual C++ compilers. + +Known supported compilers: +-------------------------- +Microsoft Visual C++ 9.0: + Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) + Microsoft Windows SDK 6.1 (x86, x64, ia64) + Microsoft Windows SDK 7.0 (x86, x64, ia64) + +Microsoft Visual C++ 10.0: + Microsoft Windows SDK 7.1 (x86, x64, ia64) + +Microsoft Visual C++ 14.X: + Microsoft Visual C++ Build Tools 2015 (x86, x64, arm) + Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64) + Microsoft Visual Studio Build Tools 2019 (x86, x64, arm, arm64) + +This may also support compilers shipped with compatible Visual Studio versions. +""" + +import json +from io import open +from os import listdir, pathsep +from os.path import join, isfile, isdir, dirname +import sys +import platform +import itertools +import distutils.errors +from setuptools.extern.packaging.version import LegacyVersion + +from setuptools.extern.six.moves import filterfalse + +from .monkey import get_unpatched + +if platform.system() == 'Windows': + from setuptools.extern.six.moves import winreg + from os import environ +else: + # Mock winreg and environ so the module can be imported on this platform. + + class winreg: + HKEY_USERS = None + HKEY_CURRENT_USER = None + HKEY_LOCAL_MACHINE = None + HKEY_CLASSES_ROOT = None + + environ = dict() + +_msvc9_suppress_errors = ( + # msvc9compiler isn't available on some platforms + ImportError, + + # msvc9compiler raises DistutilsPlatformError in some + # environments. See #1118. + distutils.errors.DistutilsPlatformError, +) + +try: + from distutils.msvc9compiler import Reg +except _msvc9_suppress_errors: + pass + + +def msvc9_find_vcvarsall(version): + """ + Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone + compiler build for Python + (VCForPython / Microsoft Visual C++ Compiler for Python 2.7). + + Fall back to original behavior when the standalone compiler is not + available. + + Redirect the path of "vcvarsall.bat". + + Parameters + ---------- + version: float + Required Microsoft Visual C++ version. + + Return + ------ + str + vcvarsall.bat path + """ + vc_base = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f' + key = vc_base % ('', version) + try: + # Per-user installs register the compiler path here + productdir = Reg.get_value(key, "installdir") + except KeyError: + try: + # All-user installs on a 64-bit system register here + key = vc_base % ('Wow6432Node\\', version) + productdir = Reg.get_value(key, "installdir") + except KeyError: + productdir = None + + if productdir: + vcvarsall = join(productdir, "vcvarsall.bat") + if isfile(vcvarsall): + return vcvarsall + + return get_unpatched(msvc9_find_vcvarsall)(version) + + +def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs): + """ + Patched "distutils.msvc9compiler.query_vcvarsall" for support extra + Microsoft Visual C++ 9.0 and 10.0 compilers. + + Set environment without use of "vcvarsall.bat". + + Parameters + ---------- + ver: float + Required Microsoft Visual C++ version. + arch: str + Target architecture. + + Return + ------ + dict + environment + """ + # Try to get environment from vcvarsall.bat (Classical way) + try: + orig = get_unpatched(msvc9_query_vcvarsall) + return orig(ver, arch, *args, **kwargs) + except distutils.errors.DistutilsPlatformError: + # Pass error if Vcvarsall.bat is missing + pass + except ValueError: + # Pass error if environment not set after executing vcvarsall.bat + pass + + # If error, try to set environment directly + try: + return EnvironmentInfo(arch, ver).return_env() + except distutils.errors.DistutilsPlatformError as exc: + _augment_exception(exc, ver, arch) + raise + + +def msvc14_get_vc_env(plat_spec): + """ + Patched "distutils._msvccompiler._get_vc_env" for support extra + Microsoft Visual C++ 14.X compilers. + + Set environment without use of "vcvarsall.bat". + + Parameters + ---------- + plat_spec: str + Target architecture. + + Return + ------ + dict + environment + """ + # Try to get environment from vcvarsall.bat (Classical way) + try: + return get_unpatched(msvc14_get_vc_env)(plat_spec) + except distutils.errors.DistutilsPlatformError: + # Pass error Vcvarsall.bat is missing + pass + + # If error, try to set environment directly + try: + return EnvironmentInfo(plat_spec, vc_min_ver=14.0).return_env() + except distutils.errors.DistutilsPlatformError as exc: + _augment_exception(exc, 14.0) + raise + + +def msvc14_gen_lib_options(*args, **kwargs): + """ + Patched "distutils._msvccompiler.gen_lib_options" for fix + compatibility between "numpy.distutils" and "distutils._msvccompiler" + (for Numpy < 1.11.2) + """ + if "numpy.distutils" in sys.modules: + import numpy as np + if LegacyVersion(np.__version__) < LegacyVersion('1.11.2'): + return np.distutils.ccompiler.gen_lib_options(*args, **kwargs) + return get_unpatched(msvc14_gen_lib_options)(*args, **kwargs) + + +def _augment_exception(exc, version, arch=''): + """ + Add details to the exception message to help guide the user + as to what action will resolve it. + """ + # Error if MSVC++ directory not found or environment not set + message = exc.args[0] + + if "vcvarsall" in message.lower() or "visual c" in message.lower(): + # Special error message if MSVC++ not installed + tmpl = 'Microsoft Visual C++ {version:0.1f} is required.' + message = tmpl.format(**locals()) + msdownload = 'www.microsoft.com/download/details.aspx?id=%d' + if version == 9.0: + if arch.lower().find('ia64') > -1: + # For VC++ 9.0, if IA64 support is needed, redirect user + # to Windows SDK 7.0. + # Note: No download link available from Microsoft. + message += ' Get it with "Microsoft Windows SDK 7.0"' + else: + # For VC++ 9.0 redirect user to Vc++ for Python 2.7 : + # This redirection link is maintained by Microsoft. + # Contact vspython@microsoft.com if it needs updating. + message += ' Get it from http://aka.ms/vcpython27' + elif version == 10.0: + # For VC++ 10.0 Redirect user to Windows SDK 7.1 + message += ' Get it with "Microsoft Windows SDK 7.1": ' + message += msdownload % 8279 + elif version >= 14.0: + # For VC++ 14.X Redirect user to latest Visual C++ Build Tools + message += (' Get it with "Build Tools for Visual Studio": ' + r'https://visualstudio.microsoft.com/downloads/') + + exc.args = (message, ) + + +class PlatformInfo: + """ + Current and Target Architectures information. + + Parameters + ---------- + arch: str + Target architecture. + """ + current_cpu = environ.get('processor_architecture', '').lower() + + def __init__(self, arch): + self.arch = arch.lower().replace('x64', 'amd64') + + @property + def target_cpu(self): + """ + Return Target CPU architecture. + + Return + ------ + str + Target CPU + """ + return self.arch[self.arch.find('_') + 1:] + + def target_is_x86(self): + """ + Return True if target CPU is x86 32 bits.. + + Return + ------ + bool + CPU is x86 32 bits + """ + return self.target_cpu == 'x86' + + def current_is_x86(self): + """ + Return True if current CPU is x86 32 bits.. + + Return + ------ + bool + CPU is x86 32 bits + """ + return self.current_cpu == 'x86' + + def current_dir(self, hidex86=False, x64=False): + """ + Current platform specific subfolder. + + Parameters + ---------- + hidex86: bool + return '' and not '\x86' if architecture is x86. + x64: bool + return '\x64' and not '\amd64' if architecture is amd64. + + Return + ------ + str + subfolder: '\target', or '' (see hidex86 parameter) + """ + return ( + '' if (self.current_cpu == 'x86' and hidex86) else + r'\x64' if (self.current_cpu == 'amd64' and x64) else + r'\%s' % self.current_cpu + ) + + def target_dir(self, hidex86=False, x64=False): + r""" + Target platform specific subfolder. + + Parameters + ---------- + hidex86: bool + return '' and not '\x86' if architecture is x86. + x64: bool + return '\x64' and not '\amd64' if architecture is amd64. + + Return + ------ + str + subfolder: '\current', or '' (see hidex86 parameter) + """ + return ( + '' if (self.target_cpu == 'x86' and hidex86) else + r'\x64' if (self.target_cpu == 'amd64' and x64) else + r'\%s' % self.target_cpu + ) + + def cross_dir(self, forcex86=False): + r""" + Cross platform specific subfolder. + + Parameters + ---------- + forcex86: bool + Use 'x86' as current architecture even if current architecture is + not x86. + + Return + ------ + str + subfolder: '' if target architecture is current architecture, + '\current_target' if not. + """ + current = 'x86' if forcex86 else self.current_cpu + return ( + '' if self.target_cpu == current else + self.target_dir().replace('\\', '\\%s_' % current) + ) + + +class RegistryInfo: + """ + Microsoft Visual Studio related registry information. + + Parameters + ---------- + platform_info: PlatformInfo + "PlatformInfo" instance. + """ + HKEYS = (winreg.HKEY_USERS, + winreg.HKEY_CURRENT_USER, + winreg.HKEY_LOCAL_MACHINE, + winreg.HKEY_CLASSES_ROOT) + + def __init__(self, platform_info): + self.pi = platform_info + + @property + def visualstudio(self): + """ + Microsoft Visual Studio root registry key. + + Return + ------ + str + Registry key + """ + return 'VisualStudio' + + @property + def sxs(self): + """ + Microsoft Visual Studio SxS registry key. + + Return + ------ + str + Registry key + """ + return join(self.visualstudio, 'SxS') + + @property + def vc(self): + """ + Microsoft Visual C++ VC7 registry key. + + Return + ------ + str + Registry key + """ + return join(self.sxs, 'VC7') + + @property + def vs(self): + """ + Microsoft Visual Studio VS7 registry key. + + Return + ------ + str + Registry key + """ + return join(self.sxs, 'VS7') + + @property + def vc_for_python(self): + """ + Microsoft Visual C++ for Python registry key. + + Return + ------ + str + Registry key + """ + return r'DevDiv\VCForPython' + + @property + def microsoft_sdk(self): + """ + Microsoft SDK registry key. + + Return + ------ + str + Registry key + """ + return 'Microsoft SDKs' + + @property + def windows_sdk(self): + """ + Microsoft Windows/Platform SDK registry key. + + Return + ------ + str + Registry key + """ + return join(self.microsoft_sdk, 'Windows') + + @property + def netfx_sdk(self): + """ + Microsoft .NET Framework SDK registry key. + + Return + ------ + str + Registry key + """ + return join(self.microsoft_sdk, 'NETFXSDK') + + @property + def windows_kits_roots(self): + """ + Microsoft Windows Kits Roots registry key. + + Return + ------ + str + Registry key + """ + return r'Windows Kits\Installed Roots' + + def microsoft(self, key, x86=False): + """ + Return key in Microsoft software registry. + + Parameters + ---------- + key: str + Registry key path where look. + x86: str + Force x86 software registry. + + Return + ------ + str + Registry key + """ + node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node' + return join('Software', node64, 'Microsoft', key) + + def lookup(self, key, name): + """ + Look for values in registry in Microsoft software registry. + + Parameters + ---------- + key: str + Registry key path where look. + name: str + Value name to find. + + Return + ------ + str + value + """ + key_read = winreg.KEY_READ + openkey = winreg.OpenKey + ms = self.microsoft + for hkey in self.HKEYS: + try: + bkey = openkey(hkey, ms(key), 0, key_read) + except (OSError, IOError): + if not self.pi.current_is_x86(): + try: + bkey = openkey(hkey, ms(key, True), 0, key_read) + except (OSError, IOError): + continue + else: + continue + try: + return winreg.QueryValueEx(bkey, name)[0] + except (OSError, IOError): + pass + + +class SystemInfo: + """ + Microsoft Windows and Visual Studio related system information. + + Parameters + ---------- + registry_info: RegistryInfo + "RegistryInfo" instance. + vc_ver: float + Required Microsoft Visual C++ version. + """ + + # Variables and properties in this class use originals CamelCase variables + # names from Microsoft source files for more easy comparison. + WinDir = environ.get('WinDir', '') + ProgramFiles = environ.get('ProgramFiles', '') + ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles) + + def __init__(self, registry_info, vc_ver=None): + self.ri = registry_info + self.pi = self.ri.pi + + self.known_vs_paths = self.find_programdata_vs_vers() + + # Except for VS15+, VC version is aligned with VS version + self.vs_ver = self.vc_ver = ( + vc_ver or self._find_latest_available_vs_ver()) + + def _find_latest_available_vs_ver(self): + """ + Find the latest VC version + + Return + ------ + float + version + """ + reg_vc_vers = self.find_reg_vs_vers() + + if not (reg_vc_vers or self.known_vs_paths): + raise distutils.errors.DistutilsPlatformError( + 'No Microsoft Visual C++ version found') + + vc_vers = set(reg_vc_vers) + vc_vers.update(self.known_vs_paths) + return sorted(vc_vers)[-1] + + def find_reg_vs_vers(self): + """ + Find Microsoft Visual Studio versions available in registry. + + Return + ------ + list of float + Versions + """ + ms = self.ri.microsoft + vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs) + vs_vers = [] + for hkey in self.ri.HKEYS: + for key in vckeys: + try: + bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ) + except (OSError, IOError): + continue + subkeys, values, _ = winreg.QueryInfoKey(bkey) + for i in range(values): + try: + ver = float(winreg.EnumValue(bkey, i)[0]) + if ver not in vs_vers: + vs_vers.append(ver) + except ValueError: + pass + for i in range(subkeys): + try: + ver = float(winreg.EnumKey(bkey, i)) + if ver not in vs_vers: + vs_vers.append(ver) + except ValueError: + pass + return sorted(vs_vers) + + def find_programdata_vs_vers(self): + r""" + Find Visual studio 2017+ versions from information in + "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances". + + Return + ------ + dict + float version as key, path as value. + """ + vs_versions = {} + instances_dir = \ + r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances' + + try: + hashed_names = listdir(instances_dir) + + except (OSError, IOError): + # Directory not exists with all Visual Studio versions + return vs_versions + + for name in hashed_names: + try: + # Get VS installation path from "state.json" file + state_path = join(instances_dir, name, 'state.json') + with open(state_path, 'rt', encoding='utf-8') as state_file: + state = json.load(state_file) + vs_path = state['installationPath'] + + # Raises OSError if this VS installation does not contain VC + listdir(join(vs_path, r'VC\Tools\MSVC')) + + # Store version and path + vs_versions[self._as_float_version( + state['installationVersion'])] = vs_path + + except (OSError, IOError, KeyError): + # Skip if "state.json" file is missing or bad format + continue + + return vs_versions + + @staticmethod + def _as_float_version(version): + """ + Return a string version as a simplified float version (major.minor) + + Parameters + ---------- + version: str + Version. + + Return + ------ + float + version + """ + return float('.'.join(version.split('.')[:2])) + + @property + def VSInstallDir(self): + """ + Microsoft Visual Studio directory. + + Return + ------ + str + path + """ + # Default path + default = join(self.ProgramFilesx86, + 'Microsoft Visual Studio %0.1f' % self.vs_ver) + + # Try to get path from registry, if fail use default path + return self.ri.lookup(self.ri.vs, '%0.1f' % self.vs_ver) or default + + @property + def VCInstallDir(self): + """ + Microsoft Visual C++ directory. + + Return + ------ + str + path + """ + path = self._guess_vc() or self._guess_vc_legacy() + + if not isdir(path): + msg = 'Microsoft Visual C++ directory not found' + raise distutils.errors.DistutilsPlatformError(msg) + + return path + + def _guess_vc(self): + """ + Locate Visual C++ for VS2017+. + + Return + ------ + str + path + """ + if self.vs_ver <= 14.0: + return '' + + try: + # First search in known VS paths + vs_dir = self.known_vs_paths[self.vs_ver] + except KeyError: + # Else, search with path from registry + vs_dir = self.VSInstallDir + + guess_vc = join(vs_dir, r'VC\Tools\MSVC') + + # Subdir with VC exact version as name + try: + # Update the VC version with real one instead of VS version + vc_ver = listdir(guess_vc)[-1] + self.vc_ver = self._as_float_version(vc_ver) + return join(guess_vc, vc_ver) + except (OSError, IOError, IndexError): + return '' + + def _guess_vc_legacy(self): + """ + Locate Visual C++ for versions prior to 2017. + + Return + ------ + str + path + """ + default = join(self.ProgramFilesx86, + r'Microsoft Visual Studio %0.1f\VC' % self.vs_ver) + + # Try to get "VC++ for Python" path from registry as default path + reg_path = join(self.ri.vc_for_python, '%0.1f' % self.vs_ver) + python_vc = self.ri.lookup(reg_path, 'installdir') + default_vc = join(python_vc, 'VC') if python_vc else default + + # Try to get path from registry, if fail use default path + return self.ri.lookup(self.ri.vc, '%0.1f' % self.vs_ver) or default_vc + + @property + def WindowsSdkVersion(self): + """ + Microsoft Windows SDK versions for specified MSVC++ version. + + Return + ------ + tuple of str + versions + """ + if self.vs_ver <= 9.0: + return '7.0', '6.1', '6.0a' + elif self.vs_ver == 10.0: + return '7.1', '7.0a' + elif self.vs_ver == 11.0: + return '8.0', '8.0a' + elif self.vs_ver == 12.0: + return '8.1', '8.1a' + elif self.vs_ver >= 14.0: + return '10.0', '8.1' + + @property + def WindowsSdkLastVersion(self): + """ + Microsoft Windows SDK last version. + + Return + ------ + str + version + """ + return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib')) + + @property + def WindowsSdkDir(self): + """ + Microsoft Windows SDK directory. + + Return + ------ + str + path + """ + sdkdir = '' + for ver in self.WindowsSdkVersion: + # Try to get it from registry + loc = join(self.ri.windows_sdk, 'v%s' % ver) + sdkdir = self.ri.lookup(loc, 'installationfolder') + if sdkdir: + break + if not sdkdir or not isdir(sdkdir): + # Try to get "VC++ for Python" version from registry + path = join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) + install_base = self.ri.lookup(path, 'installdir') + if install_base: + sdkdir = join(install_base, 'WinSDK') + if not sdkdir or not isdir(sdkdir): + # If fail, use default new path + for ver in self.WindowsSdkVersion: + intver = ver[:ver.rfind('.')] + path = r'Microsoft SDKs\Windows Kits\%s' % intver + d = join(self.ProgramFiles, path) + if isdir(d): + sdkdir = d + if not sdkdir or not isdir(sdkdir): + # If fail, use default old path + for ver in self.WindowsSdkVersion: + path = r'Microsoft SDKs\Windows\v%s' % ver + d = join(self.ProgramFiles, path) + if isdir(d): + sdkdir = d + if not sdkdir: + # If fail, use Platform SDK + sdkdir = join(self.VCInstallDir, 'PlatformSDK') + return sdkdir + + @property + def WindowsSDKExecutablePath(self): + """ + Microsoft Windows SDK executable directory. + + Return + ------ + str + path + """ + # Find WinSDK NetFx Tools registry dir name + if self.vs_ver <= 11.0: + netfxver = 35 + arch = '' + else: + netfxver = 40 + hidex86 = True if self.vs_ver <= 12.0 else False + arch = self.pi.current_dir(x64=True, hidex86=hidex86) + fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-')) + + # list all possibles registry paths + regpaths = [] + if self.vs_ver >= 14.0: + for ver in self.NetFxSdkVersion: + regpaths += [join(self.ri.netfx_sdk, ver, fx)] + + for ver in self.WindowsSdkVersion: + regpaths += [join(self.ri.windows_sdk, 'v%sA' % ver, fx)] + + # Return installation folder from the more recent path + for path in regpaths: + execpath = self.ri.lookup(path, 'installationfolder') + if execpath: + return execpath + + @property + def FSharpInstallDir(self): + """ + Microsoft Visual F# directory. + + Return + ------ + str + path + """ + path = join(self.ri.visualstudio, r'%0.1f\Setup\F#' % self.vs_ver) + return self.ri.lookup(path, 'productdir') or '' + + @property + def UniversalCRTSdkDir(self): + """ + Microsoft Universal CRT SDK directory. + + Return + ------ + str + path + """ + # Set Kit Roots versions for specified MSVC++ version + vers = ('10', '81') if self.vs_ver >= 14.0 else () + + # Find path of the more recent Kit + for ver in vers: + sdkdir = self.ri.lookup(self.ri.windows_kits_roots, + 'kitsroot%s' % ver) + if sdkdir: + return sdkdir or '' + + @property + def UniversalCRTSdkLastVersion(self): + """ + Microsoft Universal C Runtime SDK last version. + + Return + ------ + str + version + """ + return self._use_last_dir_name(join(self.UniversalCRTSdkDir, 'lib')) + + @property + def NetFxSdkVersion(self): + """ + Microsoft .NET Framework SDK versions. + + Return + ------ + tuple of str + versions + """ + # Set FxSdk versions for specified VS version + return (('4.7.2', '4.7.1', '4.7', + '4.6.2', '4.6.1', '4.6', + '4.5.2', '4.5.1', '4.5') + if self.vs_ver >= 14.0 else ()) + + @property + def NetFxSdkDir(self): + """ + Microsoft .NET Framework SDK directory. + + Return + ------ + str + path + """ + sdkdir = '' + for ver in self.NetFxSdkVersion: + loc = join(self.ri.netfx_sdk, ver) + sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder') + if sdkdir: + break + return sdkdir + + @property + def FrameworkDir32(self): + """ + Microsoft .NET Framework 32bit directory. + + Return + ------ + str + path + """ + # Default path + guess_fw = join(self.WinDir, r'Microsoft.NET\Framework') + + # Try to get path from registry, if fail use default path + return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw + + @property + def FrameworkDir64(self): + """ + Microsoft .NET Framework 64bit directory. + + Return + ------ + str + path + """ + # Default path + guess_fw = join(self.WinDir, r'Microsoft.NET\Framework64') + + # Try to get path from registry, if fail use default path + return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw + + @property + def FrameworkVersion32(self): + """ + Microsoft .NET Framework 32bit versions. + + Return + ------ + tuple of str + versions + """ + return self._find_dot_net_versions(32) + + @property + def FrameworkVersion64(self): + """ + Microsoft .NET Framework 64bit versions. + + Return + ------ + tuple of str + versions + """ + return self._find_dot_net_versions(64) + + def _find_dot_net_versions(self, bits): + """ + Find Microsoft .NET Framework versions. + + Parameters + ---------- + bits: int + Platform number of bits: 32 or 64. + + Return + ------ + tuple of str + versions + """ + # Find actual .NET version in registry + reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits) + dot_net_dir = getattr(self, 'FrameworkDir%d' % bits) + ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or '' + + # Set .NET versions for specified MSVC++ version + if self.vs_ver >= 12.0: + return ver, 'v4.0' + elif self.vs_ver >= 10.0: + return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5' + elif self.vs_ver == 9.0: + return 'v3.5', 'v2.0.50727' + elif self.vs_ver == 8.0: + return 'v3.0', 'v2.0.50727' + + @staticmethod + def _use_last_dir_name(path, prefix=''): + """ + Return name of the last dir in path or '' if no dir found. + + Parameters + ---------- + path: str + Use dirs in this path + prefix: str + Use only dirs starting by this prefix + + Return + ------ + str + name + """ + matching_dirs = ( + dir_name + for dir_name in reversed(listdir(path)) + if isdir(join(path, dir_name)) and + dir_name.startswith(prefix) + ) + return next(matching_dirs, None) or '' + + +class EnvironmentInfo: + """ + Return environment variables for specified Microsoft Visual C++ version + and platform : Lib, Include, Path and libpath. + + This function is compatible with Microsoft Visual C++ 9.0 to 14.X. + + Script created by analysing Microsoft environment configuration files like + "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ... + + Parameters + ---------- + arch: str + Target architecture. + vc_ver: float + Required Microsoft Visual C++ version. If not set, autodetect the last + version. + vc_min_ver: float + Minimum Microsoft Visual C++ version. + """ + + # Variables and properties in this class use originals CamelCase variables + # names from Microsoft source files for more easy comparison. + + def __init__(self, arch, vc_ver=None, vc_min_ver=0): + self.pi = PlatformInfo(arch) + self.ri = RegistryInfo(self.pi) + self.si = SystemInfo(self.ri, vc_ver) + + if self.vc_ver < vc_min_ver: + err = 'No suitable Microsoft Visual C++ version found' + raise distutils.errors.DistutilsPlatformError(err) + + @property + def vs_ver(self): + """ + Microsoft Visual Studio. + + Return + ------ + float + version + """ + return self.si.vs_ver + + @property + def vc_ver(self): + """ + Microsoft Visual C++ version. + + Return + ------ + float + version + """ + return self.si.vc_ver + + @property + def VSTools(self): + """ + Microsoft Visual Studio Tools. + + Return + ------ + list of str + paths + """ + paths = [r'Common7\IDE', r'Common7\Tools'] + + if self.vs_ver >= 14.0: + arch_subdir = self.pi.current_dir(hidex86=True, x64=True) + paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow'] + paths += [r'Team Tools\Performance Tools'] + paths += [r'Team Tools\Performance Tools%s' % arch_subdir] + + return [join(self.si.VSInstallDir, path) for path in paths] + + @property + def VCIncludes(self): + """ + Microsoft Visual C++ & Microsoft Foundation Class Includes. + + Return + ------ + list of str + paths + """ + return [join(self.si.VCInstallDir, 'Include'), + join(self.si.VCInstallDir, r'ATLMFC\Include')] + + @property + def VCLibraries(self): + """ + Microsoft Visual C++ & Microsoft Foundation Class Libraries. + + Return + ------ + list of str + paths + """ + if self.vs_ver >= 15.0: + arch_subdir = self.pi.target_dir(x64=True) + else: + arch_subdir = self.pi.target_dir(hidex86=True) + paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir] + + if self.vs_ver >= 14.0: + paths += [r'Lib\store%s' % arch_subdir] + + return [join(self.si.VCInstallDir, path) for path in paths] + + @property + def VCStoreRefs(self): + """ + Microsoft Visual C++ store references Libraries. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 14.0: + return [] + return [join(self.si.VCInstallDir, r'Lib\store\references')] + + @property + def VCTools(self): + """ + Microsoft Visual C++ Tools. + + Return + ------ + list of str + paths + """ + si = self.si + tools = [join(si.VCInstallDir, 'VCPackages')] + + forcex86 = True if self.vs_ver <= 10.0 else False + arch_subdir = self.pi.cross_dir(forcex86) + if arch_subdir: + tools += [join(si.VCInstallDir, 'Bin%s' % arch_subdir)] + + if self.vs_ver == 14.0: + path = 'Bin%s' % self.pi.current_dir(hidex86=True) + tools += [join(si.VCInstallDir, path)] + + elif self.vs_ver >= 15.0: + host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else + r'bin\HostX64%s') + tools += [join( + si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))] + + if self.pi.current_cpu != self.pi.target_cpu: + tools += [join( + si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))] + + else: + tools += [join(si.VCInstallDir, 'Bin')] + + return tools + + @property + def OSLibraries(self): + """ + Microsoft Windows SDK Libraries. + + Return + ------ + list of str + paths + """ + if self.vs_ver <= 10.0: + arch_subdir = self.pi.target_dir(hidex86=True, x64=True) + return [join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)] + + else: + arch_subdir = self.pi.target_dir(x64=True) + lib = join(self.si.WindowsSdkDir, 'lib') + libver = self._sdk_subdir + return [join(lib, '%sum%s' % (libver , arch_subdir))] + + @property + def OSIncludes(self): + """ + Microsoft Windows SDK Include. + + Return + ------ + list of str + paths + """ + include = join(self.si.WindowsSdkDir, 'include') + + if self.vs_ver <= 10.0: + return [include, join(include, 'gl')] + + else: + if self.vs_ver >= 14.0: + sdkver = self._sdk_subdir + else: + sdkver = '' + return [join(include, '%sshared' % sdkver), + join(include, '%sum' % sdkver), + join(include, '%swinrt' % sdkver)] + + @property + def OSLibpath(self): + """ + Microsoft Windows SDK Libraries Paths. + + Return + ------ + list of str + paths + """ + ref = join(self.si.WindowsSdkDir, 'References') + libpath = [] + + if self.vs_ver <= 9.0: + libpath += self.OSLibraries + + if self.vs_ver >= 11.0: + libpath += [join(ref, r'CommonConfiguration\Neutral')] + + if self.vs_ver >= 14.0: + libpath += [ + ref, + join(self.si.WindowsSdkDir, 'UnionMetadata'), + join(ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'), + join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'), + join(ref,'Windows.Networking.Connectivity.WwanContract', + '1.0.0.0'), + join(self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs', + '%0.1f' % self.vs_ver, 'References', 'CommonConfiguration', + 'neutral'), + ] + return libpath + + @property + def SdkTools(self): + """ + Microsoft Windows SDK Tools. + + Return + ------ + list of str + paths + """ + return list(self._sdk_tools()) + + def _sdk_tools(self): + """ + Microsoft Windows SDK Tools paths generator. + + Return + ------ + generator of str + paths + """ + if self.vs_ver < 15.0: + bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86' + yield join(self.si.WindowsSdkDir, bin_dir) + + if not self.pi.current_is_x86(): + arch_subdir = self.pi.current_dir(x64=True) + path = 'Bin%s' % arch_subdir + yield join(self.si.WindowsSdkDir, path) + + if self.vs_ver in (10.0, 11.0): + if self.pi.target_is_x86(): + arch_subdir = '' + else: + arch_subdir = self.pi.current_dir(hidex86=True, x64=True) + path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir + yield join(self.si.WindowsSdkDir, path) + + elif self.vs_ver >= 15.0: + path = join(self.si.WindowsSdkDir, 'Bin') + arch_subdir = self.pi.current_dir(x64=True) + sdkver = self.si.WindowsSdkLastVersion + yield join(path, '%s%s' % (sdkver, arch_subdir)) + + if self.si.WindowsSDKExecutablePath: + yield self.si.WindowsSDKExecutablePath + + @property + def _sdk_subdir(self): + """ + Microsoft Windows SDK version subdir. + + Return + ------ + str + subdir + """ + ucrtver = self.si.WindowsSdkLastVersion + return ('%s\\' % ucrtver) if ucrtver else '' + + @property + def SdkSetup(self): + """ + Microsoft Windows SDK Setup. + + Return + ------ + list of str + paths + """ + if self.vs_ver > 9.0: + return [] + + return [join(self.si.WindowsSdkDir, 'Setup')] + + @property + def FxTools(self): + """ + Microsoft .NET Framework Tools. + + Return + ------ + list of str + paths + """ + pi = self.pi + si = self.si + + if self.vs_ver <= 10.0: + include32 = True + include64 = not pi.target_is_x86() and not pi.current_is_x86() + else: + include32 = pi.target_is_x86() or pi.current_is_x86() + include64 = pi.current_cpu == 'amd64' or pi.target_cpu == 'amd64' + + tools = [] + if include32: + tools += [join(si.FrameworkDir32, ver) + for ver in si.FrameworkVersion32] + if include64: + tools += [join(si.FrameworkDir64, ver) + for ver in si.FrameworkVersion64] + return tools + + @property + def NetFxSDKLibraries(self): + """ + Microsoft .Net Framework SDK Libraries. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 14.0 or not self.si.NetFxSdkDir: + return [] + + arch_subdir = self.pi.target_dir(x64=True) + return [join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)] + + @property + def NetFxSDKIncludes(self): + """ + Microsoft .Net Framework SDK Includes. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 14.0 or not self.si.NetFxSdkDir: + return [] + + return [join(self.si.NetFxSdkDir, r'include\um')] + + @property + def VsTDb(self): + """ + Microsoft Visual Studio Team System Database. + + Return + ------ + list of str + paths + """ + return [join(self.si.VSInstallDir, r'VSTSDB\Deploy')] + + @property + def MSBuild(self): + """ + Microsoft Build Engine. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 12.0: + return [] + elif self.vs_ver < 15.0: + base_path = self.si.ProgramFilesx86 + arch_subdir = self.pi.current_dir(hidex86=True) + else: + base_path = self.si.VSInstallDir + arch_subdir = '' + + path = r'MSBuild\%0.1f\bin%s' % (self.vs_ver, arch_subdir) + build = [join(base_path, path)] + + if self.vs_ver >= 15.0: + # Add Roslyn C# & Visual Basic Compiler + build += [join(base_path, path, 'Roslyn')] + + return build + + @property + def HTMLHelpWorkshop(self): + """ + Microsoft HTML Help Workshop. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 11.0: + return [] + + return [join(self.si.ProgramFilesx86, 'HTML Help Workshop')] + + @property + def UCRTLibraries(self): + """ + Microsoft Universal C Runtime SDK Libraries. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 14.0: + return [] + + arch_subdir = self.pi.target_dir(x64=True) + lib = join(self.si.UniversalCRTSdkDir, 'lib') + ucrtver = self._ucrt_subdir + return [join(lib, '%sucrt%s' % (ucrtver, arch_subdir))] + + @property + def UCRTIncludes(self): + """ + Microsoft Universal C Runtime SDK Include. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 14.0: + return [] + + include = join(self.si.UniversalCRTSdkDir, 'include') + return [join(include, '%sucrt' % self._ucrt_subdir)] + + @property + def _ucrt_subdir(self): + """ + Microsoft Universal C Runtime SDK version subdir. + + Return + ------ + str + subdir + """ + ucrtver = self.si.UniversalCRTSdkLastVersion + return ('%s\\' % ucrtver) if ucrtver else '' + + @property + def FSharp(self): + """ + Microsoft Visual F#. + + Return + ------ + list of str + paths + """ + if 11.0 > self.vs_ver > 12.0: + return [] + + return [self.si.FSharpInstallDir] + + @property + def VCRuntimeRedist(self): + """ + Microsoft Visual C++ runtime redistributable dll. + + Return + ------ + str + path + """ + vcruntime = 'vcruntime%d0.dll' % self.vc_ver + arch_subdir = self.pi.target_dir(x64=True).strip('\\') + + # Installation prefixes candidates + prefixes = [] + tools_path = self.si.VCInstallDir + redist_path = dirname(tools_path.replace(r'\Tools', r'\Redist')) + if isdir(redist_path): + # Redist version may not be exactly the same as tools + redist_path = join(redist_path, listdir(redist_path)[-1]) + prefixes += [redist_path, join(redist_path, 'onecore')] + + prefixes += [join(tools_path, 'redist')] # VS14 legacy path + + # CRT directory + crt_dirs = ('Microsoft.VC%d.CRT' % (self.vc_ver * 10), + # Sometime store in directory with VS version instead of VC + 'Microsoft.VC%d.CRT' % (int(self.vs_ver) * 10)) + + # vcruntime path + for prefix, crt_dir in itertools.product(prefixes, crt_dirs): + path = join(prefix, arch_subdir, crt_dir, vcruntime) + if isfile(path): + return path + + def return_env(self, exists=True): + """ + Return environment dict. + + Parameters + ---------- + exists: bool + It True, only return existing paths. + + Return + ------ + dict + environment + """ + env = dict( + include=self._build_paths('include', + [self.VCIncludes, + self.OSIncludes, + self.UCRTIncludes, + self.NetFxSDKIncludes], + exists), + lib=self._build_paths('lib', + [self.VCLibraries, + self.OSLibraries, + self.FxTools, + self.UCRTLibraries, + self.NetFxSDKLibraries], + exists), + libpath=self._build_paths('libpath', + [self.VCLibraries, + self.FxTools, + self.VCStoreRefs, + self.OSLibpath], + exists), + path=self._build_paths('path', + [self.VCTools, + self.VSTools, + self.VsTDb, + self.SdkTools, + self.SdkSetup, + self.FxTools, + self.MSBuild, + self.HTMLHelpWorkshop, + self.FSharp], + exists), + ) + if self.vs_ver >= 14 and isfile(self.VCRuntimeRedist): + env['py_vcruntime_redist'] = self.VCRuntimeRedist + return env + + def _build_paths(self, name, spec_path_lists, exists): + """ + Given an environment variable name and specified paths, + return a pathsep-separated string of paths containing + unique, extant, directories from those paths and from + the environment variable. Raise an error if no paths + are resolved. + + Parameters + ---------- + name: str + Environment variable name + spec_path_lists: list of str + Paths + exists: bool + It True, only return existing paths. + + Return + ------ + str + Pathsep-separated paths + """ + # flatten spec_path_lists + spec_paths = itertools.chain.from_iterable(spec_path_lists) + env_paths = environ.get(name, '').split(pathsep) + paths = itertools.chain(spec_paths, env_paths) + extant_paths = list(filter(isdir, paths)) if exists else paths + if not extant_paths: + msg = "%s environment variable is empty" % name.upper() + raise distutils.errors.DistutilsPlatformError(msg) + unique_paths = self._unique_everseen(extant_paths) + return pathsep.join(unique_paths) + + # from Python docs + @staticmethod + def _unique_everseen(iterable, key=None): + """ + List unique elements, preserving order. + Remember all elements ever seen. + + _unique_everseen('AAAABBBCCDAABBB') --> A B C D + + _unique_everseen('ABBCcAD', str.lower) --> A B C D + """ + seen = set() + seen_add = seen.add + if key is None: + for element in filterfalse(seen.__contains__, iterable): + seen_add(element) + yield element + else: + for element in iterable: + k = key(element) + if k not in seen: + seen_add(k) + yield element diff --git a/robot/lib/python3.8/site-packages/setuptools/namespaces.py b/robot/lib/python3.8/site-packages/setuptools/namespaces.py new file mode 100644 index 0000000000000000000000000000000000000000..dc16106d3dc7048a160129745756bbc9b1fb51d9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/namespaces.py @@ -0,0 +1,107 @@ +import os +from distutils import log +import itertools + +from setuptools.extern.six.moves import map + + +flatten = itertools.chain.from_iterable + + +class Installer: + + nspkg_ext = '-nspkg.pth' + + def install_namespaces(self): + nsp = self._get_all_ns_packages() + if not nsp: + return + filename, ext = os.path.splitext(self._get_target()) + filename += self.nspkg_ext + self.outputs.append(filename) + log.info("Installing %s", filename) + lines = map(self._gen_nspkg_line, nsp) + + if self.dry_run: + # always generate the lines, even in dry run + list(lines) + return + + with open(filename, 'wt') as f: + f.writelines(lines) + + def uninstall_namespaces(self): + filename, ext = os.path.splitext(self._get_target()) + filename += self.nspkg_ext + if not os.path.exists(filename): + return + log.info("Removing %s", filename) + os.remove(filename) + + def _get_target(self): + return self.target + + _nspkg_tmpl = ( + "import sys, types, os", + "has_mfs = sys.version_info > (3, 5)", + "p = os.path.join(%(root)s, *%(pth)r)", + "importlib = has_mfs and __import__('importlib.util')", + "has_mfs and __import__('importlib.machinery')", + "m = has_mfs and " + "sys.modules.setdefault(%(pkg)r, " + "importlib.util.module_from_spec(" + "importlib.machinery.PathFinder.find_spec(%(pkg)r, " + "[os.path.dirname(p)])))", + "m = m or " + "sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))", + "mp = (m or []) and m.__dict__.setdefault('__path__',[])", + "(p not in mp) and mp.append(p)", + ) + "lines for the namespace installer" + + _nspkg_tmpl_multi = ( + 'm and setattr(sys.modules[%(parent)r], %(child)r, m)', + ) + "additional line(s) when a parent package is indicated" + + def _get_root(self): + return "sys._getframe(1).f_locals['sitedir']" + + def _gen_nspkg_line(self, pkg): + # ensure pkg is not a unicode string under Python 2.7 + pkg = str(pkg) + pth = tuple(pkg.split('.')) + root = self._get_root() + tmpl_lines = self._nspkg_tmpl + parent, sep, child = pkg.rpartition('.') + if parent: + tmpl_lines += self._nspkg_tmpl_multi + return ';'.join(tmpl_lines) % locals() + '\n' + + def _get_all_ns_packages(self): + """Return sorted list of all package namespaces""" + pkgs = self.distribution.namespace_packages or [] + return sorted(flatten(map(self._pkg_names, pkgs))) + + @staticmethod + def _pkg_names(pkg): + """ + Given a namespace package, yield the components of that + package. + + >>> names = Installer._pkg_names('a.b.c') + >>> set(names) == set(['a', 'a.b', 'a.b.c']) + True + """ + parts = pkg.split('.') + while parts: + yield '.'.join(parts) + parts.pop() + + +class DevelopInstaller(Installer): + def _get_root(self): + return repr(str(self.egg_path)) + + def _get_target(self): + return self.egg_link diff --git a/robot/lib/python3.8/site-packages/setuptools/package_index.py b/robot/lib/python3.8/site-packages/setuptools/package_index.py new file mode 100644 index 0000000000000000000000000000000000000000..f419d47167b39a71275744b2f2a78f85c9919a8d --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/package_index.py @@ -0,0 +1,1136 @@ +"""PyPI and direct package downloading""" +import sys +import os +import re +import shutil +import socket +import base64 +import hashlib +import itertools +import warnings +from functools import wraps + +from setuptools.extern import six +from setuptools.extern.six.moves import urllib, http_client, configparser, map + +import setuptools +from pkg_resources import ( + CHECKOUT_DIST, Distribution, BINARY_DIST, normalize_path, SOURCE_DIST, + Environment, find_distributions, safe_name, safe_version, + to_filename, Requirement, DEVELOP_DIST, EGG_DIST, +) +from setuptools import ssl_support +from distutils import log +from distutils.errors import DistutilsError +from fnmatch import translate +from setuptools.py27compat import get_all_headers +from setuptools.py33compat import unescape +from setuptools.wheel import Wheel + +__metaclass__ = type + +EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.+!]+)$') +HREF = re.compile(r"""href\s*=\s*['"]?([^'"> ]+)""", re.I) +PYPI_MD5 = re.compile( + r'([^<]+)\n\s+\(md5\)' +) +URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match +EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split() + +__all__ = [ + 'PackageIndex', 'distros_for_url', 'parse_bdist_wininst', + 'interpret_distro_name', +] + +_SOCKET_TIMEOUT = 15 + +_tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}" +user_agent = _tmpl.format(py_major='{}.{}'.format(*sys.version_info), setuptools=setuptools) + + +def parse_requirement_arg(spec): + try: + return Requirement.parse(spec) + except ValueError: + raise DistutilsError( + "Not a URL, existing file, or requirement spec: %r" % (spec,) + ) + + +def parse_bdist_wininst(name): + """Return (base,pyversion) or (None,None) for possible .exe name""" + + lower = name.lower() + base, py_ver, plat = None, None, None + + if lower.endswith('.exe'): + if lower.endswith('.win32.exe'): + base = name[:-10] + plat = 'win32' + elif lower.startswith('.win32-py', -16): + py_ver = name[-7:-4] + base = name[:-16] + plat = 'win32' + elif lower.endswith('.win-amd64.exe'): + base = name[:-14] + plat = 'win-amd64' + elif lower.startswith('.win-amd64-py', -20): + py_ver = name[-7:-4] + base = name[:-20] + plat = 'win-amd64' + return base, py_ver, plat + + +def egg_info_for_url(url): + parts = urllib.parse.urlparse(url) + scheme, server, path, parameters, query, fragment = parts + base = urllib.parse.unquote(path.split('/')[-1]) + if server == 'sourceforge.net' and base == 'download': # XXX Yuck + base = urllib.parse.unquote(path.split('/')[-2]) + if '#' in base: + base, fragment = base.split('#', 1) + return base, fragment + + +def distros_for_url(url, metadata=None): + """Yield egg or source distribution objects that might be found at a URL""" + base, fragment = egg_info_for_url(url) + for dist in distros_for_location(url, base, metadata): + yield dist + if fragment: + match = EGG_FRAGMENT.match(fragment) + if match: + for dist in interpret_distro_name( + url, match.group(1), metadata, precedence=CHECKOUT_DIST + ): + yield dist + + +def distros_for_location(location, basename, metadata=None): + """Yield egg or source distribution objects based on basename""" + if basename.endswith('.egg.zip'): + basename = basename[:-4] # strip the .zip + if basename.endswith('.egg') and '-' in basename: + # only one, unambiguous interpretation + return [Distribution.from_location(location, basename, metadata)] + if basename.endswith('.whl') and '-' in basename: + wheel = Wheel(basename) + if not wheel.is_compatible(): + return [] + return [Distribution( + location=location, + project_name=wheel.project_name, + version=wheel.version, + # Increase priority over eggs. + precedence=EGG_DIST + 1, + )] + if basename.endswith('.exe'): + win_base, py_ver, platform = parse_bdist_wininst(basename) + if win_base is not None: + return interpret_distro_name( + location, win_base, metadata, py_ver, BINARY_DIST, platform + ) + # Try source distro extensions (.zip, .tgz, etc.) + # + for ext in EXTENSIONS: + if basename.endswith(ext): + basename = basename[:-len(ext)] + return interpret_distro_name(location, basename, metadata) + return [] # no extension matched + + +def distros_for_filename(filename, metadata=None): + """Yield possible egg or source distribution objects based on a filename""" + return distros_for_location( + normalize_path(filename), os.path.basename(filename), metadata + ) + + +def interpret_distro_name( + location, basename, metadata, py_version=None, precedence=SOURCE_DIST, + platform=None +): + """Generate alternative interpretations of a source distro name + + Note: if `location` is a filesystem filename, you should call + ``pkg_resources.normalize_path()`` on it before passing it to this + routine! + """ + # Generate alternative interpretations of a source distro name + # Because some packages are ambiguous as to name/versions split + # e.g. "adns-python-1.1.0", "egenix-mx-commercial", etc. + # So, we generate each possible interepretation (e.g. "adns, python-1.1.0" + # "adns-python, 1.1.0", and "adns-python-1.1.0, no version"). In practice, + # the spurious interpretations should be ignored, because in the event + # there's also an "adns" package, the spurious "python-1.1.0" version will + # compare lower than any numeric version number, and is therefore unlikely + # to match a request for it. It's still a potential problem, though, and + # in the long run PyPI and the distutils should go for "safe" names and + # versions in distribution archive names (sdist and bdist). + + parts = basename.split('-') + if not py_version and any(re.match(r'py\d\.\d$', p) for p in parts[2:]): + # it is a bdist_dumb, not an sdist -- bail out + return + + for p in range(1, len(parts) + 1): + yield Distribution( + location, metadata, '-'.join(parts[:p]), '-'.join(parts[p:]), + py_version=py_version, precedence=precedence, + platform=platform + ) + + +# From Python 2.7 docs +def unique_everseen(iterable, key=None): + "List unique elements, preserving order. Remember all elements ever seen." + # unique_everseen('AAAABBBCCDAABBB') --> A B C D + # unique_everseen('ABBCcAD', str.lower) --> A B C D + seen = set() + seen_add = seen.add + if key is None: + for element in six.moves.filterfalse(seen.__contains__, iterable): + seen_add(element) + yield element + else: + for element in iterable: + k = key(element) + if k not in seen: + seen_add(k) + yield element + + +def unique_values(func): + """ + Wrap a function returning an iterable such that the resulting iterable + only ever yields unique items. + """ + + @wraps(func) + def wrapper(*args, **kwargs): + return unique_everseen(func(*args, **kwargs)) + + return wrapper + + +REL = re.compile(r"""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I) +# this line is here to fix emacs' cruddy broken syntax highlighting + + +@unique_values +def find_external_links(url, page): + """Find rel="homepage" and rel="download" links in `page`, yielding URLs""" + + for match in REL.finditer(page): + tag, rel = match.groups() + rels = set(map(str.strip, rel.lower().split(','))) + if 'homepage' in rels or 'download' in rels: + for match in HREF.finditer(tag): + yield urllib.parse.urljoin(url, htmldecode(match.group(1))) + + for tag in ("Home Page", "Download URL"): + pos = page.find(tag) + if pos != -1: + match = HREF.search(page, pos) + if match: + yield urllib.parse.urljoin(url, htmldecode(match.group(1))) + + +class ContentChecker: + """ + A null content checker that defines the interface for checking content + """ + + def feed(self, block): + """ + Feed a block of data to the hash. + """ + return + + def is_valid(self): + """ + Check the hash. Return False if validation fails. + """ + return True + + def report(self, reporter, template): + """ + Call reporter with information about the checker (hash name) + substituted into the template. + """ + return + + +class HashChecker(ContentChecker): + pattern = re.compile( + r'(?Psha1|sha224|sha384|sha256|sha512|md5)=' + r'(?P[a-f0-9]+)' + ) + + def __init__(self, hash_name, expected): + self.hash_name = hash_name + self.hash = hashlib.new(hash_name) + self.expected = expected + + @classmethod + def from_url(cls, url): + "Construct a (possibly null) ContentChecker from a URL" + fragment = urllib.parse.urlparse(url)[-1] + if not fragment: + return ContentChecker() + match = cls.pattern.search(fragment) + if not match: + return ContentChecker() + return cls(**match.groupdict()) + + def feed(self, block): + self.hash.update(block) + + def is_valid(self): + return self.hash.hexdigest() == self.expected + + def report(self, reporter, template): + msg = template % self.hash_name + return reporter(msg) + + +class PackageIndex(Environment): + """A distribution index that scans web pages for download URLs""" + + def __init__( + self, index_url="https://pypi.org/simple/", hosts=('*',), + ca_bundle=None, verify_ssl=True, *args, **kw + ): + Environment.__init__(self, *args, **kw) + self.index_url = index_url + "/" [:not index_url.endswith('/')] + self.scanned_urls = {} + self.fetched_urls = {} + self.package_pages = {} + self.allows = re.compile('|'.join(map(translate, hosts))).match + self.to_scan = [] + use_ssl = ( + verify_ssl + and ssl_support.is_available + and (ca_bundle or ssl_support.find_ca_bundle()) + ) + if use_ssl: + self.opener = ssl_support.opener_for(ca_bundle) + else: + self.opener = urllib.request.urlopen + + def process_url(self, url, retrieve=False): + """Evaluate a URL as a possible download, and maybe retrieve it""" + if url in self.scanned_urls and not retrieve: + return + self.scanned_urls[url] = True + if not URL_SCHEME(url): + self.process_filename(url) + return + else: + dists = list(distros_for_url(url)) + if dists: + if not self.url_ok(url): + return + self.debug("Found link: %s", url) + + if dists or not retrieve or url in self.fetched_urls: + list(map(self.add, dists)) + return # don't need the actual page + + if not self.url_ok(url): + self.fetched_urls[url] = True + return + + self.info("Reading %s", url) + self.fetched_urls[url] = True # prevent multiple fetch attempts + tmpl = "Download error on %s: %%s -- Some packages may not be found!" + f = self.open_url(url, tmpl % url) + if f is None: + return + self.fetched_urls[f.url] = True + if 'html' not in f.headers.get('content-type', '').lower(): + f.close() # not html, we can't process it + return + + base = f.url # handle redirects + page = f.read() + if not isinstance(page, str): + # In Python 3 and got bytes but want str. + if isinstance(f, urllib.error.HTTPError): + # Errors have no charset, assume latin1: + charset = 'latin-1' + else: + charset = f.headers.get_param('charset') or 'latin-1' + page = page.decode(charset, "ignore") + f.close() + for match in HREF.finditer(page): + link = urllib.parse.urljoin(base, htmldecode(match.group(1))) + self.process_url(link) + if url.startswith(self.index_url) and getattr(f, 'code', None) != 404: + page = self.process_index(url, page) + + def process_filename(self, fn, nested=False): + # process filenames or directories + if not os.path.exists(fn): + self.warn("Not found: %s", fn) + return + + if os.path.isdir(fn) and not nested: + path = os.path.realpath(fn) + for item in os.listdir(path): + self.process_filename(os.path.join(path, item), True) + + dists = distros_for_filename(fn) + if dists: + self.debug("Found: %s", fn) + list(map(self.add, dists)) + + def url_ok(self, url, fatal=False): + s = URL_SCHEME(url) + is_file = s and s.group(1).lower() == 'file' + if is_file or self.allows(urllib.parse.urlparse(url)[1]): + return True + msg = ( + "\nNote: Bypassing %s (disallowed host; see " + "http://bit.ly/2hrImnY for details).\n") + if fatal: + raise DistutilsError(msg % url) + else: + self.warn(msg, url) + + def scan_egg_links(self, search_path): + dirs = filter(os.path.isdir, search_path) + egg_links = ( + (path, entry) + for path in dirs + for entry in os.listdir(path) + if entry.endswith('.egg-link') + ) + list(itertools.starmap(self.scan_egg_link, egg_links)) + + def scan_egg_link(self, path, entry): + with open(os.path.join(path, entry)) as raw_lines: + # filter non-empty lines + lines = list(filter(None, map(str.strip, raw_lines))) + + if len(lines) != 2: + # format is not recognized; punt + return + + egg_path, setup_path = lines + + for dist in find_distributions(os.path.join(path, egg_path)): + dist.location = os.path.join(path, *lines) + dist.precedence = SOURCE_DIST + self.add(dist) + + def process_index(self, url, page): + """Process the contents of a PyPI page""" + + def scan(link): + # Process a URL to see if it's for a package page + if link.startswith(self.index_url): + parts = list(map( + urllib.parse.unquote, link[len(self.index_url):].split('/') + )) + if len(parts) == 2 and '#' not in parts[1]: + # it's a package page, sanitize and index it + pkg = safe_name(parts[0]) + ver = safe_version(parts[1]) + self.package_pages.setdefault(pkg.lower(), {})[link] = True + return to_filename(pkg), to_filename(ver) + return None, None + + # process an index page into the package-page index + for match in HREF.finditer(page): + try: + scan(urllib.parse.urljoin(url, htmldecode(match.group(1)))) + except ValueError: + pass + + pkg, ver = scan(url) # ensure this page is in the page index + if pkg: + # process individual package page + for new_url in find_external_links(url, page): + # Process the found URL + base, frag = egg_info_for_url(new_url) + if base.endswith('.py') and not frag: + if ver: + new_url += '#egg=%s-%s' % (pkg, ver) + else: + self.need_version_info(url) + self.scan_url(new_url) + + return PYPI_MD5.sub( + lambda m: '%s' % m.group(1, 3, 2), page + ) + else: + return "" # no sense double-scanning non-package pages + + def need_version_info(self, url): + self.scan_all( + "Page at %s links to .py file(s) without version info; an index " + "scan is required.", url + ) + + def scan_all(self, msg=None, *args): + if self.index_url not in self.fetched_urls: + if msg: + self.warn(msg, *args) + self.info( + "Scanning index of all packages (this may take a while)" + ) + self.scan_url(self.index_url) + + def find_packages(self, requirement): + self.scan_url(self.index_url + requirement.unsafe_name + '/') + + if not self.package_pages.get(requirement.key): + # Fall back to safe version of the name + self.scan_url(self.index_url + requirement.project_name + '/') + + if not self.package_pages.get(requirement.key): + # We couldn't find the target package, so search the index page too + self.not_found_in_index(requirement) + + for url in list(self.package_pages.get(requirement.key, ())): + # scan each page that might be related to the desired package + self.scan_url(url) + + def obtain(self, requirement, installer=None): + self.prescan() + self.find_packages(requirement) + for dist in self[requirement.key]: + if dist in requirement: + return dist + self.debug("%s does not match %s", requirement, dist) + return super(PackageIndex, self).obtain(requirement, installer) + + def check_hash(self, checker, filename, tfp): + """ + checker is a ContentChecker + """ + checker.report( + self.debug, + "Validating %%s checksum for %s" % filename) + if not checker.is_valid(): + tfp.close() + os.unlink(filename) + raise DistutilsError( + "%s validation failed for %s; " + "possible download problem?" + % (checker.hash.name, os.path.basename(filename)) + ) + + def add_find_links(self, urls): + """Add `urls` to the list that will be prescanned for searches""" + for url in urls: + if ( + self.to_scan is None # if we have already "gone online" + or not URL_SCHEME(url) # or it's a local file/directory + or url.startswith('file:') + or list(distros_for_url(url)) # or a direct package link + ): + # then go ahead and process it now + self.scan_url(url) + else: + # otherwise, defer retrieval till later + self.to_scan.append(url) + + def prescan(self): + """Scan urls scheduled for prescanning (e.g. --find-links)""" + if self.to_scan: + list(map(self.scan_url, self.to_scan)) + self.to_scan = None # from now on, go ahead and process immediately + + def not_found_in_index(self, requirement): + if self[requirement.key]: # we've seen at least one distro + meth, msg = self.info, "Couldn't retrieve index page for %r" + else: # no distros seen for this name, might be misspelled + meth, msg = ( + self.warn, + "Couldn't find index page for %r (maybe misspelled?)") + meth(msg, requirement.unsafe_name) + self.scan_all() + + def download(self, spec, tmpdir): + """Locate and/or download `spec` to `tmpdir`, returning a local path + + `spec` may be a ``Requirement`` object, or a string containing a URL, + an existing local filename, or a project/version requirement spec + (i.e. the string form of a ``Requirement`` object). If it is the URL + of a .py file with an unambiguous ``#egg=name-version`` tag (i.e., one + that escapes ``-`` as ``_`` throughout), a trivial ``setup.py`` is + automatically created alongside the downloaded file. + + If `spec` is a ``Requirement`` object or a string containing a + project/version requirement spec, this method returns the location of + a matching distribution (possibly after downloading it to `tmpdir`). + If `spec` is a locally existing file or directory name, it is simply + returned unchanged. If `spec` is a URL, it is downloaded to a subpath + of `tmpdir`, and the local filename is returned. Various errors may be + raised if a problem occurs during downloading. + """ + if not isinstance(spec, Requirement): + scheme = URL_SCHEME(spec) + if scheme: + # It's a url, download it to tmpdir + found = self._download_url(scheme.group(1), spec, tmpdir) + base, fragment = egg_info_for_url(spec) + if base.endswith('.py'): + found = self.gen_setup(found, fragment, tmpdir) + return found + elif os.path.exists(spec): + # Existing file or directory, just return it + return spec + else: + spec = parse_requirement_arg(spec) + return getattr(self.fetch_distribution(spec, tmpdir), 'location', None) + + def fetch_distribution( + self, requirement, tmpdir, force_scan=False, source=False, + develop_ok=False, local_index=None): + """Obtain a distribution suitable for fulfilling `requirement` + + `requirement` must be a ``pkg_resources.Requirement`` instance. + If necessary, or if the `force_scan` flag is set, the requirement is + searched for in the (online) package index as well as the locally + installed packages. If a distribution matching `requirement` is found, + the returned distribution's ``location`` is the value you would have + gotten from calling the ``download()`` method with the matching + distribution's URL or filename. If no matching distribution is found, + ``None`` is returned. + + If the `source` flag is set, only source distributions and source + checkout links will be considered. Unless the `develop_ok` flag is + set, development and system eggs (i.e., those using the ``.egg-info`` + format) will be ignored. + """ + # process a Requirement + self.info("Searching for %s", requirement) + skipped = {} + dist = None + + def find(req, env=None): + if env is None: + env = self + # Find a matching distribution; may be called more than once + + for dist in env[req.key]: + + if dist.precedence == DEVELOP_DIST and not develop_ok: + if dist not in skipped: + self.warn( + "Skipping development or system egg: %s", dist, + ) + skipped[dist] = 1 + continue + + test = ( + dist in req + and (dist.precedence <= SOURCE_DIST or not source) + ) + if test: + loc = self.download(dist.location, tmpdir) + dist.download_location = loc + if os.path.exists(dist.download_location): + return dist + + if force_scan: + self.prescan() + self.find_packages(requirement) + dist = find(requirement) + + if not dist and local_index is not None: + dist = find(requirement, local_index) + + if dist is None: + if self.to_scan is not None: + self.prescan() + dist = find(requirement) + + if dist is None and not force_scan: + self.find_packages(requirement) + dist = find(requirement) + + if dist is None: + self.warn( + "No local packages or working download links found for %s%s", + (source and "a source distribution of " or ""), + requirement, + ) + else: + self.info("Best match: %s", dist) + return dist.clone(location=dist.download_location) + + def fetch(self, requirement, tmpdir, force_scan=False, source=False): + """Obtain a file suitable for fulfilling `requirement` + + DEPRECATED; use the ``fetch_distribution()`` method now instead. For + backward compatibility, this routine is identical but returns the + ``location`` of the downloaded distribution instead of a distribution + object. + """ + dist = self.fetch_distribution(requirement, tmpdir, force_scan, source) + if dist is not None: + return dist.location + return None + + def gen_setup(self, filename, fragment, tmpdir): + match = EGG_FRAGMENT.match(fragment) + dists = match and [ + d for d in + interpret_distro_name(filename, match.group(1), None) if d.version + ] or [] + + if len(dists) == 1: # unambiguous ``#egg`` fragment + basename = os.path.basename(filename) + + # Make sure the file has been downloaded to the temp dir. + if os.path.dirname(filename) != tmpdir: + dst = os.path.join(tmpdir, basename) + from setuptools.command.easy_install import samefile + if not samefile(filename, dst): + shutil.copy2(filename, dst) + filename = dst + + with open(os.path.join(tmpdir, 'setup.py'), 'w') as file: + file.write( + "from setuptools import setup\n" + "setup(name=%r, version=%r, py_modules=[%r])\n" + % ( + dists[0].project_name, dists[0].version, + os.path.splitext(basename)[0] + ) + ) + return filename + + elif match: + raise DistutilsError( + "Can't unambiguously interpret project/version identifier %r; " + "any dashes in the name or version should be escaped using " + "underscores. %r" % (fragment, dists) + ) + else: + raise DistutilsError( + "Can't process plain .py files without an '#egg=name-version'" + " suffix to enable automatic setup script generation." + ) + + dl_blocksize = 8192 + + def _download_to(self, url, filename): + self.info("Downloading %s", url) + # Download the file + fp = None + try: + checker = HashChecker.from_url(url) + fp = self.open_url(url) + if isinstance(fp, urllib.error.HTTPError): + raise DistutilsError( + "Can't download %s: %s %s" % (url, fp.code, fp.msg) + ) + headers = fp.info() + blocknum = 0 + bs = self.dl_blocksize + size = -1 + if "content-length" in headers: + # Some servers return multiple Content-Length headers :( + sizes = get_all_headers(headers, 'Content-Length') + size = max(map(int, sizes)) + self.reporthook(url, filename, blocknum, bs, size) + with open(filename, 'wb') as tfp: + while True: + block = fp.read(bs) + if block: + checker.feed(block) + tfp.write(block) + blocknum += 1 + self.reporthook(url, filename, blocknum, bs, size) + else: + break + self.check_hash(checker, filename, tfp) + return headers + finally: + if fp: + fp.close() + + def reporthook(self, url, filename, blocknum, blksize, size): + pass # no-op + + def open_url(self, url, warning=None): + if url.startswith('file:'): + return local_open(url) + try: + return open_with_auth(url, self.opener) + except (ValueError, http_client.InvalidURL) as v: + msg = ' '.join([str(arg) for arg in v.args]) + if warning: + self.warn(warning, msg) + else: + raise DistutilsError('%s %s' % (url, msg)) + except urllib.error.HTTPError as v: + return v + except urllib.error.URLError as v: + if warning: + self.warn(warning, v.reason) + else: + raise DistutilsError("Download error for %s: %s" + % (url, v.reason)) + except http_client.BadStatusLine as v: + if warning: + self.warn(warning, v.line) + else: + raise DistutilsError( + '%s returned a bad status line. The server might be ' + 'down, %s' % + (url, v.line) + ) + except (http_client.HTTPException, socket.error) as v: + if warning: + self.warn(warning, v) + else: + raise DistutilsError("Download error for %s: %s" + % (url, v)) + + def _download_url(self, scheme, url, tmpdir): + # Determine download filename + # + name, fragment = egg_info_for_url(url) + if name: + while '..' in name: + name = name.replace('..', '.').replace('\\', '_') + else: + name = "__downloaded__" # default if URL has no path contents + + if name.endswith('.egg.zip'): + name = name[:-4] # strip the extra .zip before download + + filename = os.path.join(tmpdir, name) + + # Download the file + # + if scheme == 'svn' or scheme.startswith('svn+'): + return self._download_svn(url, filename) + elif scheme == 'git' or scheme.startswith('git+'): + return self._download_git(url, filename) + elif scheme.startswith('hg+'): + return self._download_hg(url, filename) + elif scheme == 'file': + return urllib.request.url2pathname(urllib.parse.urlparse(url)[2]) + else: + self.url_ok(url, True) # raises error if not allowed + return self._attempt_download(url, filename) + + def scan_url(self, url): + self.process_url(url, True) + + def _attempt_download(self, url, filename): + headers = self._download_to(url, filename) + if 'html' in headers.get('content-type', '').lower(): + return self._download_html(url, headers, filename) + else: + return filename + + def _download_html(self, url, headers, filename): + file = open(filename) + for line in file: + if line.strip(): + # Check for a subversion index page + if re.search(r'([^- ]+ - )?Revision \d+:', line): + # it's a subversion index page: + file.close() + os.unlink(filename) + return self._download_svn(url, filename) + break # not an index page + file.close() + os.unlink(filename) + raise DistutilsError("Unexpected HTML page found at " + url) + + def _download_svn(self, url, filename): + warnings.warn("SVN download support is deprecated", UserWarning) + url = url.split('#', 1)[0] # remove any fragment for svn's sake + creds = '' + if url.lower().startswith('svn:') and '@' in url: + scheme, netloc, path, p, q, f = urllib.parse.urlparse(url) + if not netloc and path.startswith('//') and '/' in path[2:]: + netloc, path = path[2:].split('/', 1) + auth, host = _splituser(netloc) + if auth: + if ':' in auth: + user, pw = auth.split(':', 1) + creds = " --username=%s --password=%s" % (user, pw) + else: + creds = " --username=" + auth + netloc = host + parts = scheme, netloc, url, p, q, f + url = urllib.parse.urlunparse(parts) + self.info("Doing subversion checkout from %s to %s", url, filename) + os.system("svn checkout%s -q %s %s" % (creds, url, filename)) + return filename + + @staticmethod + def _vcs_split_rev_from_url(url, pop_prefix=False): + scheme, netloc, path, query, frag = urllib.parse.urlsplit(url) + + scheme = scheme.split('+', 1)[-1] + + # Some fragment identification fails + path = path.split('#', 1)[0] + + rev = None + if '@' in path: + path, rev = path.rsplit('@', 1) + + # Also, discard fragment + url = urllib.parse.urlunsplit((scheme, netloc, path, query, '')) + + return url, rev + + def _download_git(self, url, filename): + filename = filename.split('#', 1)[0] + url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) + + self.info("Doing git clone from %s to %s", url, filename) + os.system("git clone --quiet %s %s" % (url, filename)) + + if rev is not None: + self.info("Checking out %s", rev) + os.system("git -C %s checkout --quiet %s" % ( + filename, + rev, + )) + + return filename + + def _download_hg(self, url, filename): + filename = filename.split('#', 1)[0] + url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) + + self.info("Doing hg clone from %s to %s", url, filename) + os.system("hg clone --quiet %s %s" % (url, filename)) + + if rev is not None: + self.info("Updating to %s", rev) + os.system("hg --cwd %s up -C -r %s -q" % ( + filename, + rev, + )) + + return filename + + def debug(self, msg, *args): + log.debug(msg, *args) + + def info(self, msg, *args): + log.info(msg, *args) + + def warn(self, msg, *args): + log.warn(msg, *args) + + +# This pattern matches a character entity reference (a decimal numeric +# references, a hexadecimal numeric reference, or a named reference). +entity_sub = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub + + +def decode_entity(match): + what = match.group(0) + return unescape(what) + + +def htmldecode(text): + """ + Decode HTML entities in the given text. + + >>> htmldecode( + ... 'https://../package_name-0.1.2.tar.gz' + ... '?tokena=A&tokenb=B">package_name-0.1.2.tar.gz') + 'https://../package_name-0.1.2.tar.gz?tokena=A&tokenb=B">package_name-0.1.2.tar.gz' + """ + return entity_sub(decode_entity, text) + + +def socket_timeout(timeout=15): + def _socket_timeout(func): + def _socket_timeout(*args, **kwargs): + old_timeout = socket.getdefaulttimeout() + socket.setdefaulttimeout(timeout) + try: + return func(*args, **kwargs) + finally: + socket.setdefaulttimeout(old_timeout) + + return _socket_timeout + + return _socket_timeout + + +def _encode_auth(auth): + """ + A function compatible with Python 2.3-3.3 that will encode + auth from a URL suitable for an HTTP header. + >>> str(_encode_auth('username%3Apassword')) + 'dXNlcm5hbWU6cGFzc3dvcmQ=' + + Long auth strings should not cause a newline to be inserted. + >>> long_auth = 'username:' + 'password'*10 + >>> chr(10) in str(_encode_auth(long_auth)) + False + """ + auth_s = urllib.parse.unquote(auth) + # convert to bytes + auth_bytes = auth_s.encode() + encoded_bytes = base64.b64encode(auth_bytes) + # convert back to a string + encoded = encoded_bytes.decode() + # strip the trailing carriage return + return encoded.replace('\n', '') + + +class Credential: + """ + A username/password pair. Use like a namedtuple. + """ + + def __init__(self, username, password): + self.username = username + self.password = password + + def __iter__(self): + yield self.username + yield self.password + + def __str__(self): + return '%(username)s:%(password)s' % vars(self) + + +class PyPIConfig(configparser.RawConfigParser): + def __init__(self): + """ + Load from ~/.pypirc + """ + defaults = dict.fromkeys(['username', 'password', 'repository'], '') + configparser.RawConfigParser.__init__(self, defaults) + + rc = os.path.join(os.path.expanduser('~'), '.pypirc') + if os.path.exists(rc): + self.read(rc) + + @property + def creds_by_repository(self): + sections_with_repositories = [ + section for section in self.sections() + if self.get(section, 'repository').strip() + ] + + return dict(map(self._get_repo_cred, sections_with_repositories)) + + def _get_repo_cred(self, section): + repo = self.get(section, 'repository').strip() + return repo, Credential( + self.get(section, 'username').strip(), + self.get(section, 'password').strip(), + ) + + def find_credential(self, url): + """ + If the URL indicated appears to be a repository defined in this + config, return the credential for that repository. + """ + for repository, cred in self.creds_by_repository.items(): + if url.startswith(repository): + return cred + + +def open_with_auth(url, opener=urllib.request.urlopen): + """Open a urllib2 request, handling HTTP authentication""" + + parsed = urllib.parse.urlparse(url) + scheme, netloc, path, params, query, frag = parsed + + # Double scheme does not raise on Mac OS X as revealed by a + # failing test. We would expect "nonnumeric port". Refs #20. + if netloc.endswith(':'): + raise http_client.InvalidURL("nonnumeric port: ''") + + if scheme in ('http', 'https'): + auth, address = _splituser(netloc) + else: + auth = None + + if not auth: + cred = PyPIConfig().find_credential(url) + if cred: + auth = str(cred) + info = cred.username, url + log.info('Authenticating as %s for %s (from .pypirc)', *info) + + if auth: + auth = "Basic " + _encode_auth(auth) + parts = scheme, address, path, params, query, frag + new_url = urllib.parse.urlunparse(parts) + request = urllib.request.Request(new_url) + request.add_header("Authorization", auth) + else: + request = urllib.request.Request(url) + + request.add_header('User-Agent', user_agent) + fp = opener(request) + + if auth: + # Put authentication info back into request URL if same host, + # so that links found on the page will work + s2, h2, path2, param2, query2, frag2 = urllib.parse.urlparse(fp.url) + if s2 == scheme and h2 == address: + parts = s2, netloc, path2, param2, query2, frag2 + fp.url = urllib.parse.urlunparse(parts) + + return fp + + +# copy of urllib.parse._splituser from Python 3.8 +def _splituser(host): + """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'.""" + user, delim, host = host.rpartition('@') + return (user if delim else None), host + + +# adding a timeout to avoid freezing package_index +open_with_auth = socket_timeout(_SOCKET_TIMEOUT)(open_with_auth) + + +def fix_sf_url(url): + return url # backward compatibility + + +def local_open(url): + """Read a local path, with special support for directories""" + scheme, server, path, param, query, frag = urllib.parse.urlparse(url) + filename = urllib.request.url2pathname(path) + if os.path.isfile(filename): + return urllib.request.urlopen(url) + elif path.endswith('/') and os.path.isdir(filename): + files = [] + for f in os.listdir(filename): + filepath = os.path.join(filename, f) + if f == 'index.html': + with open(filepath, 'r') as fp: + body = fp.read() + break + elif os.path.isdir(filepath): + f += '/' + files.append('<a href="{name}">{name}</a>'.format(name=f)) + else: + tmpl = ( + "<html><head><title>{url}" + "{files}") + body = tmpl.format(url=url, files='\n'.join(files)) + status, message = 200, "OK" + else: + status, message, body = 404, "Path not found", "Not found" + + headers = {'content-type': 'text/html'} + body_stream = six.StringIO(body) + return urllib.error.HTTPError(url, status, message, headers, body_stream) diff --git a/robot/lib/python3.8/site-packages/setuptools/py27compat.py b/robot/lib/python3.8/site-packages/setuptools/py27compat.py new file mode 100644 index 0000000000000000000000000000000000000000..1d57360f4eff13cd94a25fec989036a0b0b80523 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/py27compat.py @@ -0,0 +1,60 @@ +""" +Compatibility Support for Python 2.7 and earlier +""" + +import sys +import platform + +from setuptools.extern import six + + +def get_all_headers(message, key): + """ + Given an HTTPMessage, return all headers matching a given key. + """ + return message.get_all(key) + + +if six.PY2: + def get_all_headers(message, key): + return message.getheaders(key) + + +linux_py2_ascii = ( + platform.system() == 'Linux' and + six.PY2 +) + +rmtree_safe = str if linux_py2_ascii else lambda x: x +"""Workaround for http://bugs.python.org/issue24672""" + + +try: + from ._imp import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE + from ._imp import get_frozen_object, get_module +except ImportError: + import imp + from imp import PY_COMPILED, PY_FROZEN, PY_SOURCE # noqa + + def find_module(module, paths=None): + """Just like 'imp.find_module()', but with package support""" + parts = module.split('.') + while parts: + part = parts.pop(0) + f, path, (suffix, mode, kind) = info = imp.find_module(part, paths) + + if kind == imp.PKG_DIRECTORY: + parts = parts or ['__init__'] + paths = [path] + + elif parts: + raise ImportError("Can't find %r in %s" % (parts, module)) + + return info + + def get_frozen_object(module, paths): + return imp.get_frozen_object(module) + + def get_module(module, paths, info): + imp.load_module(module, *info) + return sys.modules[module] diff --git a/robot/lib/python3.8/site-packages/setuptools/py31compat.py b/robot/lib/python3.8/site-packages/setuptools/py31compat.py new file mode 100644 index 0000000000000000000000000000000000000000..e1da7ee2a2c56e46e09665d98ba1bc5bfedd2c3e --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/py31compat.py @@ -0,0 +1,32 @@ +__all__ = [] + +__metaclass__ = type + + +try: + # Python >=3.2 + from tempfile import TemporaryDirectory +except ImportError: + import shutil + import tempfile + + class TemporaryDirectory: + """ + Very simple temporary directory context manager. + Will try to delete afterward, but will also ignore OS and similar + errors on deletion. + """ + + def __init__(self, **kwargs): + self.name = None # Handle mkdtemp raising an exception + self.name = tempfile.mkdtemp(**kwargs) + + def __enter__(self): + return self.name + + def __exit__(self, exctype, excvalue, exctrace): + try: + shutil.rmtree(self.name, True) + except OSError: # removal errors are not the only possible + pass + self.name = None diff --git a/robot/lib/python3.8/site-packages/setuptools/py33compat.py b/robot/lib/python3.8/site-packages/setuptools/py33compat.py new file mode 100644 index 0000000000000000000000000000000000000000..cb69443638354b46b43da5bbf187b4f7cba301f1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/py33compat.py @@ -0,0 +1,59 @@ +import dis +import array +import collections + +try: + import html +except ImportError: + html = None + +from setuptools.extern import six +from setuptools.extern.six.moves import html_parser + +__metaclass__ = type + +OpArg = collections.namedtuple('OpArg', 'opcode arg') + + +class Bytecode_compat: + def __init__(self, code): + self.code = code + + def __iter__(self): + """Yield '(op,arg)' pair for each operation in code object 'code'""" + + bytes = array.array('b', self.code.co_code) + eof = len(self.code.co_code) + + ptr = 0 + extended_arg = 0 + + while ptr < eof: + + op = bytes[ptr] + + if op >= dis.HAVE_ARGUMENT: + + arg = bytes[ptr + 1] + bytes[ptr + 2] * 256 + extended_arg + ptr += 3 + + if op == dis.EXTENDED_ARG: + long_type = six.integer_types[-1] + extended_arg = arg * long_type(65536) + continue + + else: + arg = None + ptr += 1 + + yield OpArg(op, arg) + + +Bytecode = getattr(dis, 'Bytecode', Bytecode_compat) + + +unescape = getattr(html, 'unescape', None) +if unescape is None: + # HTMLParser.unescape is deprecated since Python 3.4, and will be removed + # from 3.9. + unescape = html_parser.HTMLParser().unescape diff --git a/robot/lib/python3.8/site-packages/setuptools/py34compat.py b/robot/lib/python3.8/site-packages/setuptools/py34compat.py new file mode 100644 index 0000000000000000000000000000000000000000..3ad917222a4e5bb93fe1c9e8fe1713bcab3630b6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/py34compat.py @@ -0,0 +1,13 @@ +import importlib + +try: + import importlib.util +except ImportError: + pass + + +try: + module_from_spec = importlib.util.module_from_spec +except AttributeError: + def module_from_spec(spec): + return spec.loader.load_module(spec.name) diff --git a/robot/lib/python3.8/site-packages/setuptools/sandbox.py b/robot/lib/python3.8/site-packages/setuptools/sandbox.py new file mode 100644 index 0000000000000000000000000000000000000000..685f3f72e3611a5fa99c999e233ffd179c431a6d --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/sandbox.py @@ -0,0 +1,491 @@ +import os +import sys +import tempfile +import operator +import functools +import itertools +import re +import contextlib +import pickle +import textwrap + +from setuptools.extern import six +from setuptools.extern.six.moves import builtins, map + +import pkg_resources.py31compat + +if sys.platform.startswith('java'): + import org.python.modules.posix.PosixModule as _os +else: + _os = sys.modules[os.name] +try: + _file = file +except NameError: + _file = None +_open = open +from distutils.errors import DistutilsError +from pkg_resources import working_set + + +__all__ = [ + "AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup", +] + + +def _execfile(filename, globals, locals=None): + """ + Python 3 implementation of execfile. + """ + mode = 'rb' + with open(filename, mode) as stream: + script = stream.read() + if locals is None: + locals = globals + code = compile(script, filename, 'exec') + exec(code, globals, locals) + + +@contextlib.contextmanager +def save_argv(repl=None): + saved = sys.argv[:] + if repl is not None: + sys.argv[:] = repl + try: + yield saved + finally: + sys.argv[:] = saved + + +@contextlib.contextmanager +def save_path(): + saved = sys.path[:] + try: + yield saved + finally: + sys.path[:] = saved + + +@contextlib.contextmanager +def override_temp(replacement): + """ + Monkey-patch tempfile.tempdir with replacement, ensuring it exists + """ + pkg_resources.py31compat.makedirs(replacement, exist_ok=True) + + saved = tempfile.tempdir + + tempfile.tempdir = replacement + + try: + yield + finally: + tempfile.tempdir = saved + + +@contextlib.contextmanager +def pushd(target): + saved = os.getcwd() + os.chdir(target) + try: + yield saved + finally: + os.chdir(saved) + + +class UnpickleableException(Exception): + """ + An exception representing another Exception that could not be pickled. + """ + + @staticmethod + def dump(type, exc): + """ + Always return a dumped (pickled) type and exc. If exc can't be pickled, + wrap it in UnpickleableException first. + """ + try: + return pickle.dumps(type), pickle.dumps(exc) + except Exception: + # get UnpickleableException inside the sandbox + from setuptools.sandbox import UnpickleableException as cls + return cls.dump(cls, cls(repr(exc))) + + +class ExceptionSaver: + """ + A Context Manager that will save an exception, serialized, and restore it + later. + """ + + def __enter__(self): + return self + + def __exit__(self, type, exc, tb): + if not exc: + return + + # dump the exception + self._saved = UnpickleableException.dump(type, exc) + self._tb = tb + + # suppress the exception + return True + + def resume(self): + "restore and re-raise any exception" + + if '_saved' not in vars(self): + return + + type, exc = map(pickle.loads, self._saved) + six.reraise(type, exc, self._tb) + + +@contextlib.contextmanager +def save_modules(): + """ + Context in which imported modules are saved. + + Translates exceptions internal to the context into the equivalent exception + outside the context. + """ + saved = sys.modules.copy() + with ExceptionSaver() as saved_exc: + yield saved + + sys.modules.update(saved) + # remove any modules imported since + del_modules = ( + mod_name for mod_name in sys.modules + if mod_name not in saved + # exclude any encodings modules. See #285 + and not mod_name.startswith('encodings.') + ) + _clear_modules(del_modules) + + saved_exc.resume() + + +def _clear_modules(module_names): + for mod_name in list(module_names): + del sys.modules[mod_name] + + +@contextlib.contextmanager +def save_pkg_resources_state(): + saved = pkg_resources.__getstate__() + try: + yield saved + finally: + pkg_resources.__setstate__(saved) + + +@contextlib.contextmanager +def setup_context(setup_dir): + temp_dir = os.path.join(setup_dir, 'temp') + with save_pkg_resources_state(): + with save_modules(): + hide_setuptools() + with save_path(): + with save_argv(): + with override_temp(temp_dir): + with pushd(setup_dir): + # ensure setuptools commands are available + __import__('setuptools') + yield + + +def _needs_hiding(mod_name): + """ + >>> _needs_hiding('setuptools') + True + >>> _needs_hiding('pkg_resources') + True + >>> _needs_hiding('setuptools_plugin') + False + >>> _needs_hiding('setuptools.__init__') + True + >>> _needs_hiding('distutils') + True + >>> _needs_hiding('os') + False + >>> _needs_hiding('Cython') + True + """ + pattern = re.compile(r'(setuptools|pkg_resources|distutils|Cython)(\.|$)') + return bool(pattern.match(mod_name)) + + +def hide_setuptools(): + """ + Remove references to setuptools' modules from sys.modules to allow the + invocation to import the most appropriate setuptools. This technique is + necessary to avoid issues such as #315 where setuptools upgrading itself + would fail to find a function declared in the metadata. + """ + modules = filter(_needs_hiding, sys.modules) + _clear_modules(modules) + + +def run_setup(setup_script, args): + """Run a distutils setup script, sandboxed in its directory""" + setup_dir = os.path.abspath(os.path.dirname(setup_script)) + with setup_context(setup_dir): + try: + sys.argv[:] = [setup_script] + list(args) + sys.path.insert(0, setup_dir) + # reset to include setup dir, w/clean callback list + working_set.__init__() + working_set.callbacks.append(lambda dist: dist.activate()) + + # __file__ should be a byte string on Python 2 (#712) + dunder_file = ( + setup_script + if isinstance(setup_script, str) else + setup_script.encode(sys.getfilesystemencoding()) + ) + + with DirectorySandbox(setup_dir): + ns = dict(__file__=dunder_file, __name__='__main__') + _execfile(setup_script, ns) + except SystemExit as v: + if v.args and v.args[0]: + raise + # Normal exit, just return + + +class AbstractSandbox: + """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts""" + + _active = False + + def __init__(self): + self._attrs = [ + name for name in dir(_os) + if not name.startswith('_') and hasattr(self, name) + ] + + def _copy(self, source): + for name in self._attrs: + setattr(os, name, getattr(source, name)) + + def __enter__(self): + self._copy(self) + if _file: + builtins.file = self._file + builtins.open = self._open + self._active = True + + def __exit__(self, exc_type, exc_value, traceback): + self._active = False + if _file: + builtins.file = _file + builtins.open = _open + self._copy(_os) + + def run(self, func): + """Run 'func' under os sandboxing""" + with self: + return func() + + def _mk_dual_path_wrapper(name): + original = getattr(_os, name) + + def wrap(self, src, dst, *args, **kw): + if self._active: + src, dst = self._remap_pair(name, src, dst, *args, **kw) + return original(src, dst, *args, **kw) + + return wrap + + for name in ["rename", "link", "symlink"]: + if hasattr(_os, name): + locals()[name] = _mk_dual_path_wrapper(name) + + def _mk_single_path_wrapper(name, original=None): + original = original or getattr(_os, name) + + def wrap(self, path, *args, **kw): + if self._active: + path = self._remap_input(name, path, *args, **kw) + return original(path, *args, **kw) + + return wrap + + if _file: + _file = _mk_single_path_wrapper('file', _file) + _open = _mk_single_path_wrapper('open', _open) + for name in [ + "stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir", + "remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat", + "startfile", "mkfifo", "mknod", "pathconf", "access" + ]: + if hasattr(_os, name): + locals()[name] = _mk_single_path_wrapper(name) + + def _mk_single_with_return(name): + original = getattr(_os, name) + + def wrap(self, path, *args, **kw): + if self._active: + path = self._remap_input(name, path, *args, **kw) + return self._remap_output(name, original(path, *args, **kw)) + return original(path, *args, **kw) + + return wrap + + for name in ['readlink', 'tempnam']: + if hasattr(_os, name): + locals()[name] = _mk_single_with_return(name) + + def _mk_query(name): + original = getattr(_os, name) + + def wrap(self, *args, **kw): + retval = original(*args, **kw) + if self._active: + return self._remap_output(name, retval) + return retval + + return wrap + + for name in ['getcwd', 'tmpnam']: + if hasattr(_os, name): + locals()[name] = _mk_query(name) + + def _validate_path(self, path): + """Called to remap or validate any path, whether input or output""" + return path + + def _remap_input(self, operation, path, *args, **kw): + """Called for path inputs""" + return self._validate_path(path) + + def _remap_output(self, operation, path): + """Called for path outputs""" + return self._validate_path(path) + + def _remap_pair(self, operation, src, dst, *args, **kw): + """Called for path pairs like rename, link, and symlink operations""" + return ( + self._remap_input(operation + '-from', src, *args, **kw), + self._remap_input(operation + '-to', dst, *args, **kw) + ) + + +if hasattr(os, 'devnull'): + _EXCEPTIONS = [os.devnull,] +else: + _EXCEPTIONS = [] + + +class DirectorySandbox(AbstractSandbox): + """Restrict operations to a single subdirectory - pseudo-chroot""" + + write_ops = dict.fromkeys([ + "open", "chmod", "chown", "mkdir", "remove", "unlink", "rmdir", + "utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam", + ]) + + _exception_patterns = [ + # Allow lib2to3 to attempt to save a pickled grammar object (#121) + r'.*lib2to3.*\.pickle$', + ] + "exempt writing to paths that match the pattern" + + def __init__(self, sandbox, exceptions=_EXCEPTIONS): + self._sandbox = os.path.normcase(os.path.realpath(sandbox)) + self._prefix = os.path.join(self._sandbox, '') + self._exceptions = [ + os.path.normcase(os.path.realpath(path)) + for path in exceptions + ] + AbstractSandbox.__init__(self) + + def _violation(self, operation, *args, **kw): + from setuptools.sandbox import SandboxViolation + raise SandboxViolation(operation, args, kw) + + if _file: + + def _file(self, path, mode='r', *args, **kw): + if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): + self._violation("file", path, mode, *args, **kw) + return _file(path, mode, *args, **kw) + + def _open(self, path, mode='r', *args, **kw): + if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): + self._violation("open", path, mode, *args, **kw) + return _open(path, mode, *args, **kw) + + def tmpnam(self): + self._violation("tmpnam") + + def _ok(self, path): + active = self._active + try: + self._active = False + realpath = os.path.normcase(os.path.realpath(path)) + return ( + self._exempted(realpath) + or realpath == self._sandbox + or realpath.startswith(self._prefix) + ) + finally: + self._active = active + + def _exempted(self, filepath): + start_matches = ( + filepath.startswith(exception) + for exception in self._exceptions + ) + pattern_matches = ( + re.match(pattern, filepath) + for pattern in self._exception_patterns + ) + candidates = itertools.chain(start_matches, pattern_matches) + return any(candidates) + + def _remap_input(self, operation, path, *args, **kw): + """Called for path inputs""" + if operation in self.write_ops and not self._ok(path): + self._violation(operation, os.path.realpath(path), *args, **kw) + return path + + def _remap_pair(self, operation, src, dst, *args, **kw): + """Called for path pairs like rename, link, and symlink operations""" + if not self._ok(src) or not self._ok(dst): + self._violation(operation, src, dst, *args, **kw) + return (src, dst) + + def open(self, file, flags, mode=0o777, *args, **kw): + """Called for low-level os.open()""" + if flags & WRITE_FLAGS and not self._ok(file): + self._violation("os.open", file, flags, mode, *args, **kw) + return _os.open(file, flags, mode, *args, **kw) + + +WRITE_FLAGS = functools.reduce( + operator.or_, [getattr(_os, a, 0) for a in + "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()] +) + + +class SandboxViolation(DistutilsError): + """A setup script attempted to modify the filesystem outside the sandbox""" + + tmpl = textwrap.dedent(""" + SandboxViolation: {cmd}{args!r} {kwargs} + + The package setup script has attempted to modify files on your system + that are not within the EasyInstall build area, and has been aborted. + + This package cannot be safely installed by EasyInstall, and may not + support alternate installation locations even if you run its setup + script by hand. Please inform the package's author and the EasyInstall + maintainers to find out if a fix or workaround is available. + """).lstrip() + + def __str__(self): + cmd, args, kwargs = self.args + return self.tmpl.format(**locals()) diff --git a/robot/lib/python3.8/site-packages/setuptools/script (dev).tmpl b/robot/lib/python3.8/site-packages/setuptools/script (dev).tmpl new file mode 100644 index 0000000000000000000000000000000000000000..39a24b04888e79df51e2237577b303a2f901be63 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/script (dev).tmpl @@ -0,0 +1,6 @@ +# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r +__requires__ = %(spec)r +__import__('pkg_resources').require(%(spec)r) +__file__ = %(dev_path)r +with open(__file__) as f: + exec(compile(f.read(), __file__, 'exec')) diff --git a/robot/lib/python3.8/site-packages/setuptools/script.tmpl b/robot/lib/python3.8/site-packages/setuptools/script.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..ff5efbcab3b58063dd84787181c26a95fb663d94 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/script.tmpl @@ -0,0 +1,3 @@ +# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r +__requires__ = %(spec)r +__import__('pkg_resources').run_script(%(spec)r, %(script_name)r) diff --git a/robot/lib/python3.8/site-packages/setuptools/site-patch.py b/robot/lib/python3.8/site-packages/setuptools/site-patch.py new file mode 100644 index 0000000000000000000000000000000000000000..40b00de0a799686485b266fd92abb9fb100ed718 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/site-patch.py @@ -0,0 +1,74 @@ +def __boot(): + import sys + import os + PYTHONPATH = os.environ.get('PYTHONPATH') + if PYTHONPATH is None or (sys.platform == 'win32' and not PYTHONPATH): + PYTHONPATH = [] + else: + PYTHONPATH = PYTHONPATH.split(os.pathsep) + + pic = getattr(sys, 'path_importer_cache', {}) + stdpath = sys.path[len(PYTHONPATH):] + mydir = os.path.dirname(__file__) + + for item in stdpath: + if item == mydir or not item: + continue # skip if current dir. on Windows, or my own directory + importer = pic.get(item) + if importer is not None: + loader = importer.find_module('site') + if loader is not None: + # This should actually reload the current module + loader.load_module('site') + break + else: + try: + import imp # Avoid import loop in Python 3 + stream, path, descr = imp.find_module('site', [item]) + except ImportError: + continue + if stream is None: + continue + try: + # This should actually reload the current module + imp.load_module('site', stream, path, descr) + finally: + stream.close() + break + else: + raise ImportError("Couldn't find the real 'site' module") + + known_paths = dict([(makepath(item)[1], 1) for item in sys.path]) # 2.2 comp + + oldpos = getattr(sys, '__egginsert', 0) # save old insertion position + sys.__egginsert = 0 # and reset the current one + + for item in PYTHONPATH: + addsitedir(item) + + sys.__egginsert += oldpos # restore effective old position + + d, nd = makepath(stdpath[0]) + insert_at = None + new_path = [] + + for item in sys.path: + p, np = makepath(item) + + if np == nd and insert_at is None: + # We've hit the first 'system' path entry, so added entries go here + insert_at = len(new_path) + + if np in known_paths or insert_at is None: + new_path.append(item) + else: + # new path after the insert point, back-insert it + new_path.insert(insert_at, item) + insert_at += 1 + + sys.path[:] = new_path + + +if __name__ == 'site': + __boot() + del __boot diff --git a/robot/lib/python3.8/site-packages/setuptools/ssl_support.py b/robot/lib/python3.8/site-packages/setuptools/ssl_support.py new file mode 100644 index 0000000000000000000000000000000000000000..226db694bb38791147c6bf2881c4b86025dd2f8f --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/ssl_support.py @@ -0,0 +1,260 @@ +import os +import socket +import atexit +import re +import functools + +from setuptools.extern.six.moves import urllib, http_client, map, filter + +from pkg_resources import ResolutionError, ExtractionError + +try: + import ssl +except ImportError: + ssl = None + +__all__ = [ + 'VerifyingHTTPSHandler', 'find_ca_bundle', 'is_available', 'cert_paths', + 'opener_for' +] + +cert_paths = """ +/etc/pki/tls/certs/ca-bundle.crt +/etc/ssl/certs/ca-certificates.crt +/usr/share/ssl/certs/ca-bundle.crt +/usr/local/share/certs/ca-root.crt +/etc/ssl/cert.pem +/System/Library/OpenSSL/certs/cert.pem +/usr/local/share/certs/ca-root-nss.crt +/etc/ssl/ca-bundle.pem +""".strip().split() + +try: + HTTPSHandler = urllib.request.HTTPSHandler + HTTPSConnection = http_client.HTTPSConnection +except AttributeError: + HTTPSHandler = HTTPSConnection = object + +is_available = ssl is not None and object not in (HTTPSHandler, HTTPSConnection) + + +try: + from ssl import CertificateError, match_hostname +except ImportError: + try: + from backports.ssl_match_hostname import CertificateError + from backports.ssl_match_hostname import match_hostname + except ImportError: + CertificateError = None + match_hostname = None + +if not CertificateError: + + class CertificateError(ValueError): + pass + + +if not match_hostname: + + def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + https://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + # Ported from python3-syntax: + # leftmost, *remainder = dn.split(r'.') + parts = dn.split(r'.') + leftmost = parts[0] + remainder = parts[1:] + + wildcards = leftmost.count('*') + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn)) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + elif leftmost.startswith('xn--') or hostname.startswith('xn--'): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + return pat.match(hostname) + + def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate") + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError("hostname %r " + "doesn't match either of %s" + % (hostname, ', '.join(map(repr, dnsnames)))) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r " + "doesn't match %r" + % (hostname, dnsnames[0])) + else: + raise CertificateError("no appropriate commonName or " + "subjectAltName fields were found") + + +class VerifyingHTTPSHandler(HTTPSHandler): + """Simple verifying handler: no auth, subclasses, timeouts, etc.""" + + def __init__(self, ca_bundle): + self.ca_bundle = ca_bundle + HTTPSHandler.__init__(self) + + def https_open(self, req): + return self.do_open( + lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle, **kw), req + ) + + +class VerifyingHTTPSConn(HTTPSConnection): + """Simple verifying connection: no auth, subclasses, timeouts, etc.""" + + def __init__(self, host, ca_bundle, **kw): + HTTPSConnection.__init__(self, host, **kw) + self.ca_bundle = ca_bundle + + def connect(self): + sock = socket.create_connection( + (self.host, self.port), getattr(self, 'source_address', None) + ) + + # Handle the socket if a (proxy) tunnel is present + if hasattr(self, '_tunnel') and getattr(self, '_tunnel_host', None): + self.sock = sock + self._tunnel() + # http://bugs.python.org/issue7776: Python>=3.4.1 and >=2.7.7 + # change self.host to mean the proxy server host when tunneling is + # being used. Adapt, since we are interested in the destination + # host for the match_hostname() comparison. + actual_host = self._tunnel_host + else: + actual_host = self.host + + if hasattr(ssl, 'create_default_context'): + ctx = ssl.create_default_context(cafile=self.ca_bundle) + self.sock = ctx.wrap_socket(sock, server_hostname=actual_host) + else: + # This is for python < 2.7.9 and < 3.4? + self.sock = ssl.wrap_socket( + sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle + ) + try: + match_hostname(self.sock.getpeercert(), actual_host) + except CertificateError: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + raise + + +def opener_for(ca_bundle=None): + """Get a urlopen() replacement that uses ca_bundle for verification""" + return urllib.request.build_opener( + VerifyingHTTPSHandler(ca_bundle or find_ca_bundle()) + ).open + + +# from jaraco.functools +def once(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + if not hasattr(func, 'always_returns'): + func.always_returns = func(*args, **kwargs) + return func.always_returns + return wrapper + + +@once +def get_win_certfile(): + try: + import wincertstore + except ImportError: + return None + + class CertFile(wincertstore.CertFile): + def __init__(self): + super(CertFile, self).__init__() + atexit.register(self.close) + + def close(self): + try: + super(CertFile, self).close() + except OSError: + pass + + _wincerts = CertFile() + _wincerts.addstore('CA') + _wincerts.addstore('ROOT') + return _wincerts.name + + +def find_ca_bundle(): + """Return an existing CA bundle path, or None""" + extant_cert_paths = filter(os.path.isfile, cert_paths) + return ( + get_win_certfile() + or next(extant_cert_paths, None) + or _certifi_where() + ) + + +def _certifi_where(): + try: + return __import__('certifi').where() + except (ImportError, ResolutionError, ExtractionError): + pass diff --git a/robot/lib/python3.8/site-packages/setuptools/unicode_utils.py b/robot/lib/python3.8/site-packages/setuptools/unicode_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..7c63efd20b350358ab25c079166dbb00ef49f8d2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/unicode_utils.py @@ -0,0 +1,44 @@ +import unicodedata +import sys + +from setuptools.extern import six + + +# HFS Plus uses decomposed UTF-8 +def decompose(path): + if isinstance(path, six.text_type): + return unicodedata.normalize('NFD', path) + try: + path = path.decode('utf-8') + path = unicodedata.normalize('NFD', path) + path = path.encode('utf-8') + except UnicodeError: + pass # Not UTF-8 + return path + + +def filesys_decode(path): + """ + Ensure that the given path is decoded, + NONE when no expected encoding works + """ + + if isinstance(path, six.text_type): + return path + + fs_enc = sys.getfilesystemencoding() or 'utf-8' + candidates = fs_enc, 'utf-8' + + for enc in candidates: + try: + return path.decode(enc) + except UnicodeDecodeError: + continue + + +def try_encode(string, enc): + "turn unicode encoding into a functional routine" + try: + return string.encode(enc) + except UnicodeEncodeError: + return None diff --git a/robot/lib/python3.8/site-packages/setuptools/version.py b/robot/lib/python3.8/site-packages/setuptools/version.py new file mode 100644 index 0000000000000000000000000000000000000000..95e1869658566aac3060562d8cd5a6b647887d1e --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/version.py @@ -0,0 +1,6 @@ +import pkg_resources + +try: + __version__ = pkg_resources.get_distribution('setuptools').version +except Exception: + __version__ = 'unknown' diff --git a/robot/lib/python3.8/site-packages/setuptools/wheel.py b/robot/lib/python3.8/site-packages/setuptools/wheel.py new file mode 100644 index 0000000000000000000000000000000000000000..025aaa828a24cb7746e5fac9b66984d5b9794bc3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/wheel.py @@ -0,0 +1,220 @@ +"""Wheels support.""" + +from distutils.util import get_platform +from distutils import log +import email +import itertools +import os +import posixpath +import re +import zipfile + +import pkg_resources +import setuptools +from pkg_resources import parse_version +from setuptools.extern.packaging.tags import sys_tags +from setuptools.extern.packaging.utils import canonicalize_name +from setuptools.extern.six import PY3 +from setuptools.command.egg_info import write_requirements + + +__metaclass__ = type + + +WHEEL_NAME = re.compile( + r"""^(?P.+?)-(?P\d.*?) + ((-(?P\d.*?))?-(?P.+?)-(?P.+?)-(?P.+?) + )\.whl$""", + re.VERBOSE).match + +NAMESPACE_PACKAGE_INIT = '''\ +try: + __import__('pkg_resources').declare_namespace(__name__) +except ImportError: + __path__ = __import__('pkgutil').extend_path(__path__, __name__) +''' + + +def unpack(src_dir, dst_dir): + '''Move everything under `src_dir` to `dst_dir`, and delete the former.''' + for dirpath, dirnames, filenames in os.walk(src_dir): + subdir = os.path.relpath(dirpath, src_dir) + for f in filenames: + src = os.path.join(dirpath, f) + dst = os.path.join(dst_dir, subdir, f) + os.renames(src, dst) + for n, d in reversed(list(enumerate(dirnames))): + src = os.path.join(dirpath, d) + dst = os.path.join(dst_dir, subdir, d) + if not os.path.exists(dst): + # Directory does not exist in destination, + # rename it and prune it from os.walk list. + os.renames(src, dst) + del dirnames[n] + # Cleanup. + for dirpath, dirnames, filenames in os.walk(src_dir, topdown=True): + assert not filenames + os.rmdir(dirpath) + + +class Wheel: + + def __init__(self, filename): + match = WHEEL_NAME(os.path.basename(filename)) + if match is None: + raise ValueError('invalid wheel name: %r' % filename) + self.filename = filename + for k, v in match.groupdict().items(): + setattr(self, k, v) + + def tags(self): + '''List tags (py_version, abi, platform) supported by this wheel.''' + return itertools.product( + self.py_version.split('.'), + self.abi.split('.'), + self.platform.split('.'), + ) + + def is_compatible(self): + '''Is the wheel is compatible with the current platform?''' + supported_tags = set((t.interpreter, t.abi, t.platform) for t in sys_tags()) + return next((True for t in self.tags() if t in supported_tags), False) + + def egg_name(self): + return pkg_resources.Distribution( + project_name=self.project_name, version=self.version, + platform=(None if self.platform == 'any' else get_platform()), + ).egg_name() + '.egg' + + def get_dist_info(self, zf): + # find the correct name of the .dist-info dir in the wheel file + for member in zf.namelist(): + dirname = posixpath.dirname(member) + if (dirname.endswith('.dist-info') and + canonicalize_name(dirname).startswith( + canonicalize_name(self.project_name))): + return dirname + raise ValueError("unsupported wheel format. .dist-info not found") + + def install_as_egg(self, destination_eggdir): + '''Install wheel as an egg directory.''' + with zipfile.ZipFile(self.filename) as zf: + self._install_as_egg(destination_eggdir, zf) + + def _install_as_egg(self, destination_eggdir, zf): + dist_basename = '%s-%s' % (self.project_name, self.version) + dist_info = self.get_dist_info(zf) + dist_data = '%s.data' % dist_basename + egg_info = os.path.join(destination_eggdir, 'EGG-INFO') + + self._convert_metadata(zf, destination_eggdir, dist_info, egg_info) + self._move_data_entries(destination_eggdir, dist_data) + self._fix_namespace_packages(egg_info, destination_eggdir) + + @staticmethod + def _convert_metadata(zf, destination_eggdir, dist_info, egg_info): + def get_metadata(name): + with zf.open(posixpath.join(dist_info, name)) as fp: + value = fp.read().decode('utf-8') if PY3 else fp.read() + return email.parser.Parser().parsestr(value) + + wheel_metadata = get_metadata('WHEEL') + # Check wheel format version is supported. + wheel_version = parse_version(wheel_metadata.get('Wheel-Version')) + wheel_v1 = ( + parse_version('1.0') <= wheel_version < parse_version('2.0dev0') + ) + if not wheel_v1: + raise ValueError( + 'unsupported wheel format version: %s' % wheel_version) + # Extract to target directory. + os.mkdir(destination_eggdir) + zf.extractall(destination_eggdir) + # Convert metadata. + dist_info = os.path.join(destination_eggdir, dist_info) + dist = pkg_resources.Distribution.from_location( + destination_eggdir, dist_info, + metadata=pkg_resources.PathMetadata(destination_eggdir, dist_info), + ) + + # Note: Evaluate and strip markers now, + # as it's difficult to convert back from the syntax: + # foobar; "linux" in sys_platform and extra == 'test' + def raw_req(req): + req.marker = None + return str(req) + install_requires = list(sorted(map(raw_req, dist.requires()))) + extras_require = { + extra: sorted( + req + for req in map(raw_req, dist.requires((extra,))) + if req not in install_requires + ) + for extra in dist.extras + } + os.rename(dist_info, egg_info) + os.rename( + os.path.join(egg_info, 'METADATA'), + os.path.join(egg_info, 'PKG-INFO'), + ) + setup_dist = setuptools.Distribution( + attrs=dict( + install_requires=install_requires, + extras_require=extras_require, + ), + ) + # Temporarily disable info traces. + log_threshold = log._global_log.threshold + log.set_threshold(log.WARN) + try: + write_requirements( + setup_dist.get_command_obj('egg_info'), + None, + os.path.join(egg_info, 'requires.txt'), + ) + finally: + log.set_threshold(log_threshold) + + @staticmethod + def _move_data_entries(destination_eggdir, dist_data): + """Move data entries to their correct location.""" + dist_data = os.path.join(destination_eggdir, dist_data) + dist_data_scripts = os.path.join(dist_data, 'scripts') + if os.path.exists(dist_data_scripts): + egg_info_scripts = os.path.join( + destination_eggdir, 'EGG-INFO', 'scripts') + os.mkdir(egg_info_scripts) + for entry in os.listdir(dist_data_scripts): + # Remove bytecode, as it's not properly handled + # during easy_install scripts install phase. + if entry.endswith('.pyc'): + os.unlink(os.path.join(dist_data_scripts, entry)) + else: + os.rename( + os.path.join(dist_data_scripts, entry), + os.path.join(egg_info_scripts, entry), + ) + os.rmdir(dist_data_scripts) + for subdir in filter(os.path.exists, ( + os.path.join(dist_data, d) + for d in ('data', 'headers', 'purelib', 'platlib') + )): + unpack(subdir, destination_eggdir) + if os.path.exists(dist_data): + os.rmdir(dist_data) + + @staticmethod + def _fix_namespace_packages(egg_info, destination_eggdir): + namespace_packages = os.path.join( + egg_info, 'namespace_packages.txt') + if os.path.exists(namespace_packages): + with open(namespace_packages) as fp: + namespace_packages = fp.read().split() + for mod in namespace_packages: + mod_dir = os.path.join(destination_eggdir, *mod.split('.')) + mod_init = os.path.join(mod_dir, '__init__.py') + if not os.path.exists(mod_dir): + os.mkdir(mod_dir) + if not os.path.exists(mod_init): + with open(mod_init, 'w') as fp: + fp.write(NAMESPACE_PACKAGE_INIT) diff --git a/robot/lib/python3.8/site-packages/setuptools/windows_support.py b/robot/lib/python3.8/site-packages/setuptools/windows_support.py new file mode 100644 index 0000000000000000000000000000000000000000..cb977cff9545ef5d48ad7cf13f2cbe1ebc3e7cd0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/setuptools/windows_support.py @@ -0,0 +1,29 @@ +import platform +import ctypes + + +def windows_only(func): + if platform.system() != 'Windows': + return lambda *args, **kwargs: None + return func + + +@windows_only +def hide_file(path): + """ + Set the hidden attribute on a file or directory. + + From http://stackoverflow.com/questions/19622133/ + + `path` must be text. + """ + __import__('ctypes.wintypes') + SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW + SetFileAttributes.argtypes = ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD + SetFileAttributes.restype = ctypes.wintypes.BOOL + + FILE_ATTRIBUTE_HIDDEN = 0x02 + + ret = SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN) + if not ret: + raise ctypes.WinError() diff --git a/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/AUTHORS.txt b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/AUTHORS.txt new file mode 100644 index 0000000000000000000000000000000000000000..72c87d7d38ae7bf859717c333a5ee8230f6ce624 --- /dev/null +++ b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/AUTHORS.txt @@ -0,0 +1,562 @@ +A_Rog +Aakanksha Agrawal <11389424+rasponic@users.noreply.github.com> +Abhinav Sagar <40603139+abhinavsagar@users.noreply.github.com> +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Tse +Adam Wentz +admin +Adrien Morison +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Aleks Bunin +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Alexey Popravka +Alli +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrey Bulgakov +Andrés Delfino <34587441+andresdelfino@users.noreply.github.com> +Andrés Delfino +Andy Freeland +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anuj Godase +AQNOUCH Mohammed +AraHaan +Arindam Choudhury +Armin Ronacher +Artem +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avner Cohen +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Darnell +Ben Hoyt +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Brandon L. Reiss +Brandt Bucher +Brett Randall +Brian Cristante <33549821+brcrista@users.noreply.github.com> +Brian Cristante +Brian Rosner +BrownTruck +Bruno Oliveira +Bruno Renié +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +c22 +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Chih-Hsuan Yen +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris McDonough +Chris Wolfe +Christian Heimes +Christian Oudard +Christopher Hunt +Christopher Snyder +Clark Boylan +Clay McClure +Cody +Cody Soyland +Colin Watson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Curtis Doty +cytolentino +Damian Quiroga +Dan Black +Dan Savilonis +Dan Sully +daniel +Daniel Collins +Daniel Hahler +Daniel Holth +Daniel Jost +Daniel Shaulov +Daniele Esposti +Daniele Procida +Danny Hermes +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Bordeynik +David Caro +David Evans +David Linke +David Pursehouse +David Tucker +David Wales +Davidovich +derwolfe +Desetude +Diego Caraballo +DiegoCaraballo +Dmitry Gladkov +Domen Kožar +Donald Stufft +Dongweiming +Douglas Thor +DrFeathers +Dustin Ingram +Dwayne Bailey +Ed Morley <501702+edmorley@users.noreply.github.com> +Ed Morley +Eitan Adler +ekristina +elainechan +Eli Schwartz +Eli Schwartz +Emil Burzo +Emil Styrke +Endoh Takanao +enoch +Erdinc Mutlu +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Ernest W Durbin III +Ernest W. Durbin III +Erwin Janssen +Eugene Vereshchagin +everdimension +Felix Yan +fiber-space +Filip Kokosiński +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gdanielson +Geoffrey Lehée +Geoffrey Sneddon +George Song +Georgi Valkov +Giftlin Rajaiah +gizmoguy1 +gkdoc <40815324+gkdoc@users.noreply.github.com> +Gopinath M <31352222+mgopi1990@users.noreply.github.com> +GOTO Hayato <3532528+gh640@users.noreply.github.com> +gpiks +Guilherme Espada +Guy Rozendorn +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +Herbert Pfennig +Hsiaoming Yang +Hugo +Hugo Lopes Tavares +Hugo van Kemenade +hugovk +Hynek Schlawack +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ilya Baryshev +INADA Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Ivan Pozdeev +Jacob Kim +jakirkham +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James Cleveland +James Cleveland +James Firth +James Polley +Jan Pokorný +Jannis Leidel +jarondl +Jason R. Coombs +Jay Graves +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jelmer Vernooij +jenix21 +Jeremy Stanley +Jeremy Zafran +Jiashuo Li +Jim Garrison +Jivan Amara +John Paton +John-Scott Atlakson +johnthagen +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joost Molenaar +Jorge Niedbalski +Joseph Long +Josh Bronson +Josh Hansen +Josh Schneier +Juanjo Bazán +Julian Berman +Julian Gethmann +Julien Demoor +jwg4 +Jyrki Pulliainen +Kai Chen +Kamal Bin Mustafa +kaustav haldar +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kexuan Sun +Kit Randel +kpinc +Krishna Oza +Kumar McMillan +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurie Opperman +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +Loren Carvalho +Lucas Cimon +Ludovic Gasc +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +László Kiss Kollár +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark Williams +Mark Williams +Markus Hametner +Masaki +Masklinn +Matej Stuchlik +Mathew Jennings +Mathieu Bridon +Matt Good +Matt Maker +Matt Robenolt +matthew +Matthew Einhorn +Matthew Gilliard +Matthew Iversen +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna <44498973+mbaluna@users.noreply.github.com> +mdebi <17590103+mdebi@users.noreply.github.com> +memoselyk +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Williamson +michaelpacer +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Monica Baluna +montefra +Monty Taylor +Nate Coraor +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nikhil Benesch +Nitesh Sharma +Nowell Strite +NtaleGrey +nvdv +Ofekmeister +ofrinevo +Oliver Jeeves +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +OMOTO Kenji +Omry Yadan +Oren Held +Oscar Benjamin +Oz N Tiram +Pachwenko <32424503+Pachwenko@users.noreply.github.com> +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavithra Eswaramoorthy <33131404+QueenCoffee@users.noreply.github.com> +Pawel Jasinski +Pekka Klärck +Peter Lisák +Peter Waller +petr-tik +Phaneendra Chiruvella +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Pratik Mallya +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal <7895pulkit@gmail.com> +Qiangning Hong +Quentin Pradet +R. David Murray +Rafael Caricio +Ralf Schmitt +Razzi Abuissa +rdb +Remi Rampin +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Richard Jones +RobberPhex +Robert Collins +Robert McGibbon +Robert T. McGibbon +robin elisha robinson +Roey Berman +Rohan Jain +Rohan Jain +Rohan Jain +Roman Bogorodskiy +Romuald Brunet +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +Roy Wellington Ⅳ +Ryan Wooden +ryneeverett +Sachi King +Salvatore Rinchiera +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sergey Vasilyev +Seth Woodworth +Shlomi Fish +Shovan Maity +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +Sorin Sbarnea +Stavros Korokithakis +Stefan Scherfke +Stephan Erb +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +stonebig +Stéphane Bidoul (ACSONE) +Stéphane Bidoul +Stéphane Klein +Sumana Harihareswara +Sviatoslav Sydorenko +Sviatoslav Sydorenko +Swat009 +Takayuki SHIMIZUKAWA +tbeswick +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tom Forbes +Tom Freudenheim +Tom V +Tomas Orsava +Tomer Chachamu +Tony Beswick +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +Travis Swicegood +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Philippon +Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> +Vitaly Babiy +Vladimir Rutsky +W. Trevor King +Wil Tan +Wilfred Hughes +William ML Leslie +William T Olson +Wilson Mo +wim glenn +Wolfgang Maier +Xavier Fernandez +Xavier Fernandez +xoviat +xtreak +YAMAMOTO Takashi +Yen Chi Hsuan +Yeray Diaz Diaz +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Zearin +Zearin +Zhiping Deng +Zvezdan Petkovic +Łukasz Langa +Семён Марьясин diff --git a/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..737fec5c5352af3d9a6a47a0670da4bdb52c5725 --- /dev/null +++ b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2019 The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/METADATA b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..b9c24c7317332c74c806f2a8d8b16eab73b2e3d3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/METADATA @@ -0,0 +1,49 @@ +Metadata-Version: 2.1 +Name: six +Version: 1.14.0 +Summary: Python 2 and 3 compatibility utilities +Home-page: https://github.com/benjaminp/six +Author: Benjamin Peterson +Author-email: benjamin@python.org +License: MIT +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.* + +.. image:: https://img.shields.io/pypi/v/six.svg + :target: https://pypi.org/project/six/ + :alt: six on PyPI + +.. image:: https://travis-ci.org/benjaminp/six.svg?branch=master + :target: https://travis-ci.org/benjaminp/six + :alt: six on TravisCI + +.. image:: https://readthedocs.org/projects/six/badge/?version=latest + :target: https://six.readthedocs.io/ + :alt: six's documentation on Read the Docs + +.. image:: https://img.shields.io/badge/license-MIT-green.svg + :target: https://github.com/benjaminp/six/blob/master/LICENSE + :alt: MIT License badge + +Six is a Python 2 and 3 compatibility library. It provides utility functions +for smoothing over the differences between the Python versions with the goal of +writing Python code that is compatible on both Python versions. See the +documentation for more information on what is provided. + +Six supports Python 2.7 and 3.3+. It is contained in only one Python +file, so it can be easily copied into your project. (The copyright and license +notice must be retained.) + +Online documentation is at https://six.readthedocs.io/. + +Bugs can be reported to https://github.com/benjaminp/six. The code can also +be found there. + + diff --git a/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/RECORD b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..90f02f49fa879150aa91ae772df9f1fe6f68a4f4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/RECORD @@ -0,0 +1,11 @@ +six.py,sha256=Q6WvEXZ1DGEASAo3CGNCJkKv2tPy8xkSmK-VHE9PYIA,34074 +six-1.14.0.dist-info/AUTHORS.txt,sha256=RtqU9KfonVGhI48DAA4-yTOBUhBtQTjFhaDzHoyh7uU,21518 +six-1.14.0.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090 +six-1.14.0.dist-info/METADATA,sha256=nEAc9huAtn13Bna3MQ1ZSswoyaV7GMJdKfGluHFX4DU,1795 +six-1.14.0.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +six-1.14.0.dist-info/top_level.txt,sha256=_iVH_iYEtEXnD8nYGQYpYFUvkUW9sEO1GYbkeKSAais,4 +six-1.14.0.dist-info/RECORD,, +six-1.14.0.dist-info/INSTALLER,, +six-1.14.0.dist-info/__pycache__,, +six-1.14.0.virtualenv,, +six.cpython-38.pyc,, \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/WHEEL b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ef99c6cf3283b50a273ac4c6d009a0aa85597070 --- /dev/null +++ b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..ffe2fce498955b628014618b28c6bcf152466a4a --- /dev/null +++ b/robot/lib/python3.8/site-packages/six-1.14.0.dist-info/top_level.txt @@ -0,0 +1 @@ +six diff --git a/robot/lib/python3.8/site-packages/six-1.14.0.virtualenv b/robot/lib/python3.8/site-packages/six-1.14.0.virtualenv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/six.py b/robot/lib/python3.8/site-packages/six.py new file mode 100644 index 0000000000000000000000000000000000000000..5fe9f8e141ee13756fec536415adc446bfc109d7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/six.py @@ -0,0 +1,980 @@ +# Copyright (c) 2010-2020 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Utilities for writing code that runs on Python 2 and 3""" + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.14.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getoutput", "commands", "subprocess"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), + MovedAttribute("parse_http_list", "urllib2", "urllib.request"), + MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + unichr = chr + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + del io + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" + _assertNotRegex = "assertNotRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +def assertNotRegex(self, *args, **kwargs): + return getattr(self, _assertNotRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""") + + +if sys.version_info[:2] > (3,): + exec_("""def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + # This does exactly the same what the :func:`py3:functools.update_wrapper` + # function does on Python versions after 3.2. It sets the ``__wrapped__`` + # attribute on ``wrapper`` object and it doesn't raise an error if any of + # the attributes mentioned in ``assigned`` and ``updated`` are missing on + # ``wrapped`` object. + def _update_wrapper(wrapper, wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + continue + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + wrapper.__wrapped__ = wrapped + return wrapper + _update_wrapper.__doc__ = functools.update_wrapper.__doc__ + + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + return functools.partial(_update_wrapper, wrapped=wrapped, + assigned=assigned, updated=updated) + wraps.__doc__ = functools.wraps.__doc__ + +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(type): + + def __new__(cls, name, this_bases, d): + if sys.version_info[:2] >= (3, 7): + # This version introduced PEP 560 that requires a bit + # of extra care (we mimic what is done by __build_class__). + resolved_bases = types.resolve_bases(bases) + if resolved_bases is not bases: + d['__orig_bases__'] = bases + else: + resolved_bases = bases + return meta(name, resolved_bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + if hasattr(cls, '__qualname__'): + orig_vars['__qualname__'] = cls.__qualname__ + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def ensure_binary(s, encoding='utf-8', errors='strict'): + """Coerce **s** to six.binary_type. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> encoded to `bytes` + - `bytes` -> `bytes` + """ + if isinstance(s, text_type): + return s.encode(encoding, errors) + elif isinstance(s, binary_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + +def ensure_str(s, encoding='utf-8', errors='strict'): + """Coerce *s* to `str`. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) + if PY2 and isinstance(s, text_type): + s = s.encode(encoding, errors) + elif PY3 and isinstance(s, binary_type): + s = s.decode(encoding, errors) + return s + + +def ensure_text(s, encoding='utf-8', errors='strict'): + """Coerce *s* to six.text_type. + + For Python 2: + - `unicode` -> `unicode` + - `str` -> `unicode` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if isinstance(s, binary_type): + return s.decode(encoding, errors) + elif isinstance(s, text_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + +def python_2_unicode_compatible(klass): + """ + A class decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/AUTHORS b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/AUTHORS new file mode 100644 index 0000000000000000000000000000000000000000..375500d3637b6c6ef7a8d652fc9ebf82c1163681 --- /dev/null +++ b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/AUTHORS @@ -0,0 +1,2 @@ +Daniel Richman +Adam Greig diff --git a/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/LICENSE b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..10926e87f113fb026c366866d6fa466061562870 --- /dev/null +++ b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/LICENSE @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/METADATA b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..c8af5841ee4fcd949d804106fd84093963516607 --- /dev/null +++ b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/METADATA @@ -0,0 +1,154 @@ +Metadata-Version: 2.1 +Name: strict-rfc3339 +Version: 0.7 +Summary: Strict, simple, lightweight RFC3339 functions +Home-page: http://www.danielrichman.co.uk/libraries/strict-rfc3339.html +Author: Daniel Richman, Adam Greig +Author-email: main@danielrichman.co.uk +License: GNU General Public License Version 3 +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 + +Strict, simple, lightweight RFC3339 functions +============================================= + +Goals +----- + + - Convert unix timestamps to and from RFC3339. + - Either produce RFC3339 strings with a UTC offset (Z) or with the offset + that the C time module reports is the local timezone offset. + - Simple with minimal dependencies/libraries. + - Avoid timezones as much as possible. + - Be very strict and follow RFC3339. + +Caveats +------- + + - Leap seconds are not quite supported, since timestamps do not support them, + and it requires access to timezone data. + - You may be limited by the size of `time_t` on 32 bit systems. + +In both cases, see 'Notes' below. + +Rationale +--------- + + - A lot of libraries have trouble with DST transitions and ambiguous times. + - Generally, using the python datetime object causes trouble, introducing + problems with timezones. + - The excellent `pytz` library seems to achieve timezone perfection, however + it didn't (at the time of writing) have a method for getting the local + timezone or the 'now' time in the local zone. + - I saw a lot of problems ultimately due to information lost when converting + or transferring between two libraries (e.g., `time` -> `datetime` loses DST + info in the tuple) + +Usage +----- + +Validation: + + >>> strict_rfc3339.validate_rfc3339("some rubbish") + False + >>> strict_rfc3339.validate_rfc3339("2013-03-25T12:42:31+00:32") + True + +Indeed, we can then: + + >>> strict_rfc3339.rfc3339_to_timestamp("2013-03-25T12:42:31+00:32") + 1364213431 + >>> tuple(time.gmtime(1364213431))[:6] + (2013, 3, 25, 12, 10, 31) + +No need for two function calls: + + >>> strict_rfc3339.rfc3339_to_timestamp("some rubbish") + Traceback [...] + strict_rfc3339.InvalidRFC3339Error + +Producing strings (for this example `TZ=America/New_York`): + + >>> strict_rfc3339.timestamp_to_rfc3339_utcoffset(1364213431) + '2013-03-25T12:10:31Z' + >>> strict_rfc3339.timestamp_to_rfc3339_localoffset(1364213431) + '2013-03-25T08:10:31-04:00' + +And with `TZ=Europe/London`: + + >>> strict_rfc3339.timestamp_to_rfc3339_localoffset(1364213431) + '2013-03-25T12:10:31+00:00' + +Convenience functions: + + >>> strict_rfc3339.now_to_rfc3339_utcoffset() + '2013-03-25T21:39:35Z' + >>> strict_rfc3339.now_to_rfc3339_localoffset() + '2013-03-25T17:39:39-04:00' + +Floats: + + >>> strict_rfc3339.now_to_rfc3339_utcoffset(integer=True) # The default + '2013-03-25T22:04:01Z' + >>> strict_rfc3339.now_to_rfc3339_utcoffset(integer=False) + '2013-03-25T22:04:01.04399Z' + >>> strict_rfc3339.rfc3339_to_timestamp("2013-03-25T22:04:10.04399Z") + 1364249050.0439899 + +Behind the scenes +----------------- + +These functions are essentially string formatting and arithmetic only. A very +small number of functions do the heavy lifting. These come from two modules: +`time` and `calendar`. + +`time` is a thin wrapper around the C time functions. I'm working on the +assumption that these are usually of high quality and are correct. From the +`time` module, `strict_rfc3339` uses: + + - `time`: (actually calls `gettimeofday`) to get the current timestamp / "now" + - `gmtime`: splits a timestamp into a UTC time tuple + - `localtime`: splits a timestamp into a local time tuple + +Based on the assumption that they are correct, we can use the difference +between the values returned by `gmtime` and `localtime` to find the local +offset. As clunky as it sounds, it's far easier than using a fully fledged +timezone library. + +`calendar` is implemented in python. From `calendar`, `strict_rfc3339` uses: + + - `timegm`: turns a UTC time tuple into a timestamp. This essentially just + multiplies each number in the tuple by the number of seconds in it. It does + use `datetime.date` to work out the number of days between Jan 1 1970 and the + Y-M-D in the tuple, but this is fine. It does not perform much validation at + all. + - `monthrange`: gives the number of days in a (year, month). I checked and + (at least in my copy of python 2.6) the function used for leap years is + identical to the one specified in RFC3339 itself. + +Notes +----- + + - RFC3339 specifies an offset, not a timezone, and the difference is + important. Timezones are evil. + - It is perhaps simpler to think of a RFC3339 string as a human readable + method of specifying a moment in time (only). These functions merely provide + access to the one-to-many timestamp-to-RFC3339 mapping. + - Timestamps don't support leap seconds: a day is always 86400 "long". + Also, validating leap seconds is particularly fiddly, because not only do + you need some data, but it must be kept up to date. + For this reason, `strict_rfc3339` does not support leap seconds: in validation, + `seconds == 60` or `seconds == 61` is rejected. + In the case of reverse leap seconds, calendar.timegm will blissfully accept + it. The result would be about as correct as you could get. + - RFC3339 generation using `gmtime` or `localtime` may be limited by the size + of `time_t` on the system: if it is 32 bit, you're limited to dates between + (approx) 1901 and 2038. This does not affect `rfc3339_to_timestamp`. + + diff --git a/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/RECORD b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..70b8214487f231a9f9bf9858faffe317f67fee55 --- /dev/null +++ b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/RECORD @@ -0,0 +1,9 @@ +__pycache__/strict_rfc3339.cpython-38.pyc,, +strict_rfc3339-0.7.dist-info/AUTHORS,sha256=cSJfL5lkGTo22F6o4WryA28XLFdOPcM7onmCXVNO2wg,74 +strict_rfc3339-0.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +strict_rfc3339-0.7.dist-info/LICENSE,sha256=CuBIWlvTemPmNgNZZBfk6w5lMzT6bH-TLKOg6F1K8ic,35148 +strict_rfc3339-0.7.dist-info/METADATA,sha256=uYm6rUglbYdYLkpRoB4ADwQX087vHCHSLq8LQNq4gEc,5789 +strict_rfc3339-0.7.dist-info/RECORD,, +strict_rfc3339-0.7.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92 +strict_rfc3339-0.7.dist-info/top_level.txt,sha256=6M2RuEWe3GQqQ5HF8c635BLNXT8gASQ76FYKijiqEc0,15 +strict_rfc3339.py,sha256=jsZWUiPam2puvii5UoJlyS91WtJmpZ7wgDD7oe2RcSY,6150 diff --git a/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/WHEEL b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..b552003ff90e66227ec90d1b159324f140d46001 --- /dev/null +++ b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..520bba449dd2bf227da016a93d249ba3c51493a4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/strict_rfc3339-0.7.dist-info/top_level.txt @@ -0,0 +1 @@ +strict_rfc3339 diff --git a/robot/lib/python3.8/site-packages/strict_rfc3339.py b/robot/lib/python3.8/site-packages/strict_rfc3339.py new file mode 100644 index 0000000000000000000000000000000000000000..4558c01b3d7da61925446069458bb5a7dc34f1d3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/strict_rfc3339.py @@ -0,0 +1,202 @@ +# Copyright 2012 (C) Daniel Richman, Adam Greig +# +# This file is part of strict_rfc3339. +# +# strict_rfc3339 is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# strict_rfc3339 is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with strict_rfc3339. If not, see . + +""" +Super simple lightweight RFC3339 functions +""" + +import re +import time +import calendar + +__all__ = ["validate_rfc3339", + "InvalidRFC3339Error", + "rfc3339_to_timestamp", + "timestamp_to_rfc3339_utcoffset", + "timestamp_to_rfc3339_localoffset", + "now_to_rfc3339_utcoffset", + "now_to_rfc3339_localoffset"] + +rfc3339_regex = re.compile( + r"^(\d\d\d\d)\-(\d\d)\-(\d\d)T" + r"(\d\d):(\d\d):(\d\d)(\.\d+)?(Z|([+\-])(\d\d):(\d\d))$") + + +def validate_rfc3339(datestring): + """Check an RFC3339 string is valid via a regex and some range checks""" + + m = rfc3339_regex.match(datestring) + if m is None: + return False + + groups = m.groups() + + year, month, day, hour, minute, second = [int(i) for i in groups[:6]] + + if not 1 <= year <= 9999: + # Have to reject this, unfortunately (despite it being OK by rfc3339): + # calendar.timegm/calendar.monthrange can't cope (since datetime can't) + return False + + if not 1 <= month <= 12: + return False + + (_, max_day) = calendar.monthrange(year, month) + if not 1 <= day <= max_day: + return False + + if not (0 <= hour <= 23 and 0 <= minute <= 59 and 0 <= second <= 59): + # forbid leap seconds :-(. See README + return False + + if groups[7] != "Z": + (offset_sign, offset_hours, offset_mins) = groups[8:] + if not (0 <= int(offset_hours) <= 23 and 0 <= int(offset_mins) <= 59): + return False + + # all OK + return True + + +class InvalidRFC3339Error(ValueError): + """Subclass of ValueError thrown by rfc3339_to_timestamp""" + pass + + +def rfc3339_to_timestamp(datestring): + """Convert an RFC3339 date-time string to a UTC UNIX timestamp""" + + if not validate_rfc3339(datestring): + raise InvalidRFC3339Error + + groups = rfc3339_regex.match(datestring).groups() + + time_tuple = [int(p) for p in groups[:6]] + timestamp = calendar.timegm(time_tuple) + + seconds_part = groups[6] + if seconds_part is not None: + timestamp += float("0" + seconds_part) + + if groups[7] != "Z": + (offset_sign, offset_hours, offset_mins) = groups[8:] + offset_seconds = int(offset_hours) * 3600 + int(offset_mins) * 60 + if offset_sign == '-': + offset_seconds = -offset_seconds + timestamp -= offset_seconds + + return timestamp + + +def _seconds_and_microseconds(timestamp): + """ + Split a floating point timestamp into an integer number of seconds since + the epoch, and an integer number of microseconds (having rounded to the + nearest microsecond). + + If `_seconds_and_microseconds(x) = (y, z)` then the following holds (up to + the error introduced by floating point operations): + + * `x = y + z / 1_000_000.` + * `0 <= z < 1_000_000.` + """ + + if isinstance(timestamp, int): + return (timestamp, 0) + else: + timestamp_us = int(round(timestamp * 1e6)) + return divmod(timestamp_us, 1000000) + +def _make_datestring_start(time_tuple, microseconds): + ds_format = "{0:04d}-{1:02d}-{2:02d}T{3:02d}:{4:02d}:{5:02d}" + datestring = ds_format.format(*time_tuple) + + seconds_part_str = "{0:06d}".format(microseconds) + # There used to be a bug here where it could be 1000000 + assert len(seconds_part_str) == 6 and seconds_part_str[0] != '-' + seconds_part_str = seconds_part_str.rstrip("0") + if seconds_part_str != "": + datestring += "." + seconds_part_str + + return datestring + + +def timestamp_to_rfc3339_utcoffset(timestamp): + """Convert a UTC UNIX timestamp to RFC3339, with the offset as 'Z'""" + + seconds, microseconds = _seconds_and_microseconds(timestamp) + + time_tuple = time.gmtime(seconds) + datestring = _make_datestring_start(time_tuple, microseconds) + datestring += "Z" + + assert abs(rfc3339_to_timestamp(datestring) - timestamp) < 0.000001 + return datestring + + +def timestamp_to_rfc3339_localoffset(timestamp): + """ + Convert a UTC UNIX timestamp to RFC3339, using the local offset. + + localtime() provides the time parts. The difference between gmtime and + localtime tells us the offset. + """ + + seconds, microseconds = _seconds_and_microseconds(timestamp) + + time_tuple = time.localtime(seconds) + datestring = _make_datestring_start(time_tuple, microseconds) + + gm_time_tuple = time.gmtime(seconds) + offset = calendar.timegm(time_tuple) - calendar.timegm(gm_time_tuple) + + if abs(offset) % 60 != 0: + raise ValueError("Your local offset is not a whole minute") + + offset_minutes = abs(offset) // 60 + offset_hours = offset_minutes // 60 + offset_minutes %= 60 + + offset_string = "{0:02d}:{1:02d}".format(offset_hours, offset_minutes) + + if offset < 0: + datestring += "-" + else: + datestring += "+" + + datestring += offset_string + assert abs(rfc3339_to_timestamp(datestring) - timestamp) < 0.000001 + + return datestring + + +def now_to_rfc3339_utcoffset(integer=True): + """Convert the current time to RFC3339, with the offset as 'Z'""" + + timestamp = time.time() + if integer: + timestamp = int(timestamp) + return timestamp_to_rfc3339_utcoffset(timestamp) + + +def now_to_rfc3339_localoffset(integer=True): + """Convert the current time to RFC3339, using the local offset.""" + + timestamp = time.time() + if integer: + timestamp = int(timestamp) + return timestamp_to_rfc3339_localoffset(timestamp) diff --git a/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/METADATA b/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..9e21ee5271310f3e2ad1d04bc12756ad90c408f8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/METADATA @@ -0,0 +1,1147 @@ +Metadata-Version: 2.1 +Name: tox +Version: 3.0.0 +Summary: virtualenv-based automation of test activities +Home-page: https://tox.readthedocs.org/ +Author: holger krekel +Author-email: holger@merlinux.eu +License: http://opensource.org/licenses/MIT +Platform: unix +Platform: linux +Platform: osx +Platform: cygwin +Platform: win32 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: POSIX +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Topic :: Software Development :: Testing +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +Provides-Extra: testing +Provides-Extra: lint +Provides-Extra: publish +Provides-Extra: docs +Requires-Dist: py (>=1.4.17) +Requires-Dist: pluggy (<1.0,>=0.3.0) +Requires-Dist: six +Requires-Dist: virtualenv (>=1.11.2) +Provides-Extra: docs +Requires-Dist: sphinx (<2,>=1.6.3); extra == 'docs' +Requires-Dist: towncrier (>=17.8.0); extra == 'docs' +Provides-Extra: lint +Requires-Dist: flake8 (==3.4.1); extra == 'lint' +Requires-Dist: flake8-bugbear (==17.4.0); extra == 'lint' +Requires-Dist: pre-commit (==1.4.4); extra == 'lint' +Provides-Extra: publish +Requires-Dist: devpi; extra == 'publish' +Requires-Dist: twine; extra == 'publish' +Provides-Extra: testing +Requires-Dist: pytest (>=3.0.0); extra == 'testing' +Requires-Dist: pytest-cov; extra == 'testing' +Requires-Dist: pytest-mock; extra == 'testing' +Requires-Dist: pytest-timeout; extra == 'testing' +Requires-Dist: pytest-xdist; extra == 'testing' + +.. image:: https://img.shields.io/pypi/v/tox.svg + :target: https://pypi.org/project/tox/ +.. image:: https://img.shields.io/pypi/pyversions/tox.svg + :target: https://pypi.org/project/tox/ +.. image:: https://travis-ci.org/tox-dev/tox.svg?branch=master + :target: https://travis-ci.org/tox-dev/tox +.. image:: https://img.shields.io/appveyor/ci/RonnyPfannschmidt/tox/master.svg + :target: https://ci.appveyor.com/project/RonnyPfannschmidt/tox +.. image:: https://codecov.io/gh/tox-dev/tox/branch/master/graph/badge.svg + :target: https://codecov.io/gh/tox-dev/tox +.. image:: https://readthedocs.org/projects/tox/badge/?version=latest + :target: http://tox.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + +tox automation project +====================== + +**vision: standardize testing in Python** + +tox aims to automate and standardize testing in Python. It is part of a larger vision of easing the packaging, testing and release process of Python software. + +What is tox? +============ + +tox is a generic virtualenv management and test command line tool you can use for: + +* checking your package installs correctly with different Python versions and + interpreters + +* running your tests in each of the environments, configuring your test tool of choice + +* acting as a frontend to Continuous Integration servers, greatly + reducing boilerplate and merging CI and shell-based testing. + +For more information and the repository please see: + +- home and docs: https://tox.readthedocs.org + +- repository: https://github.com/tox-dev/tox + + +CHANGELOG +========= + +Versions follow `Semantic Versioning `_ (..). + +Backward incompatible (breaking) changes will only be introduced in major versions +with advance notice in the **Deprecations** section of releases. + +Changes that already arrived in master, but are not released yet can be found as fragments in the +`"changelog" directory `_. + +If you want to know more details: since version 2.8 releases are organized as Github projects. +To see all closed issues and merged pull requests, contained in a release, visit the projects +on Github: + +- `3.0 series of releases `_ +- `2.9 series of releases `_ +- `2.8 series of releases `_ + +.. + Everything below here is generated by `towncrier `_. + It is generated once as part of the release process rendering fragments from the `changelog` + folder. If necessary, the generated text can be edited afterwards to e.g. merge rc changes + into the final release notes. + +.. towncrier release notes start + +3.0.0 (2018-04-02) +------------------ + +Bugfixes +^^^^^^^^ + +- Write directly to stdout buffer if possible to prevent str vs bytes issues - + by @asottile (`#426 `_) +- fix #672 reporting to json file when skip-missing-interpreters option is used + - by @r2dan (`#672 `_) +- avoid ``Requested Python version (X.Y) not installed`` stderr output when a + Python environment is looked up using the ``py`` Python launcher on Windows + and the environment is not found installed on the system - by + @jurko-gospodnetic (`#692 `_) +- Fixed an issue where invocation of Tox from the Python package, where + invocation errors (failed actions) occur results in a change in the + sys.stdout stream encoding in Python 3.x. New behaviour is that sys.stdout is + reset back to its original encoding after invocation errors - by @tonybaloney + (`#723 `_) +- The reading of command output sometimes failed with ``IOError: [Errno 0] + Error`` on Windows, this was fixed by using a simpler method to update the + read buffers. - by @fschulze (`#727 + `_) +- (only affected rc releases) fix up tox.cmdline to be callable without args - by + @gaborbernat. (`#773 `_) +- (only affected rc releases) Revert breaking change of tox.cmdline not callable + with no args - by @gaborbernat. (`#773 `_) +- (only affected rc releases) fix #755 by reverting the ``cmdline`` import to the old + location and changing the entry point instead - by @fschulze + (`#755 `_) + + +Features +^^^^^^^^ + +- ``tox`` displays exit code together with ``InvocationError`` - by @blueyed + and @ederag. (`#290 `_) +- Hint for possible signal upon ``InvocationError``, on posix systems - by + @ederag and @asottile. (`#766 `_) +- Add a ``-q`` option to progressively silence tox's output. For each time you + specify ``-q`` to tox, the output provided by tox reduces. This option allows + you to see only your command output without the default verbosity of what tox + is doing. This also counter-acts usage of ``-v``. For example, running ``tox + -v -q ...`` will provide you with the default verbosity. ``tox -vv -q`` is + equivalent to ``tox -v``. By @sigmavirus24 (`#256 + `_) +- add support for negated factor conditions, e.g. ``!dev: production_log`` - by + @jurko-gospodnetic (`#292 `_) +- Headings like ``installed: `` will not be printed if there is no + output to display after the :, unless verbosity is set. By @cryvate (`#601 + `_) +- Allow spaces in command line options to pip in deps. Where previously only + ``deps=-rreq.txt`` and ``deps=--requirement=req.txt`` worked, now also + ``deps=-r req.txt`` and ``deps=--requirement req.txt`` work - by @cryvate + (`#668 `_) +- drop Python ``2.6`` and ``3.3`` support: ``setuptools`` dropped supporting + these, and as we depend on it we'll follow up with doing the same (use ``tox + <= 2.9.1`` if you still need this support) - by @gaborbernat (`#679 + `_) +- Add tox_runenvreport as a possible plugin, allowing the overriding of the + default behaviour to execute a command to get the installed packages within a + virtual environment - by @tonybaloney (`#725 + `_) +- Forward ``PROCESSOR_ARCHITECTURE`` by default on Windows to fix + ``platform.machine()``. (`#740 `_) + + +Documentation improvements +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Change favicon to the vector beach ball - by @hazalozturk + (`#748 `_) +- Change sphinx theme to alabaster and add logo/favicon - by @hazalozturk + (`#639 `_) + + +Miscellaneous / trivial changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Running ``tox`` without a ``setup.py`` now has a more friendly error message + and gives troubleshooting suggestions - by @Volcyy. + (`#331 `_) +- Fix pycodestyle (formerly pep8) errors E741 (ambiguous variable names, in + this case, 'l's) and remove ignore of this error in tox.ini - by @cryvate + (`#663 `_) +- touched up ``interpreters.py`` code and added some missing tests for it - by + @jurko-gospodnetic (`#708 `_) +- The ``PYTHONDONTWRITEBYTECODE`` environment variable is no longer unset - by + @stephenfin. (`#744 `_) + + +2.9.1 (2017-09-29) +------------------ + +Misc +^^^^ + +- integrated new release process and fixed changelog rendering for pypi.org - + by `@obestwalter `_. + + +2.9.0 (2017-09-29) +------------------ + +Features +^^^^^^^^ + +- ``tox --version`` now shows information about all registered plugins - by + `@obestwalter `_ + (`#544 `_) + + +Bugfixes +^^^^^^^^ + +- ``skip_install`` overrides ``usedevelop`` (``usedevelop`` is an option to + choose the installation type if the package is installed and `skip_install` + determines if it should be installed at all) - by `@ferdonline `_ + (`#571 `_) + + +Misc +^^^^ + +- `#635 `_ inherit from correct exception - + by `@obestwalter `_ + (`#635 `_). +- spelling and escape sequence fixes - by `@scoop `_ + (`#637 `_ and + `#638 `_). +- add a badge to show build status of documentation on readthedocs.io - + by `@obestwalter `_. + + +Improved Documentation +^^^^^^^^^^^^^^^^^^^^^^ + +- add `towncrier `_ to allow adding + changelog entries with the pull requests without generating merge conflicts; + with this release notes are now grouped into four distinct collections: + `Features`, `Bugfixes`, `Improved Documentation` and `Deprecations and + Removals`. (`#614 `_) + + +2.8.2 (2017-10-09) +------------------ + +- `#466 `_: stop env var leakage if popen failed with resultjson or redirect + +2.8.1 (2017-09-04) +------------------ + +- `pull request 599 `_: fix problems with implementation of `#515 `_. + Substitutions from other sections were not made anymore if they were not in `envlist`. + Thanks to Clark Boylan (`@cboylan `_) for helping to get this fixed (`pull request 597 `_). + +2.8.0 (2017-09-01) +------------------- + +- `#276 `_: Remove easy_install from docs (TL;DR: use pip). Thanks Martin Andrysík (`@sifuraz `_). + +- `#301 `_: Expand nested substitutions in ``tox.ini``. Thanks `@vlaci `_. Thanks to Eli Collins + (`@eli-collins `_) for creating a reproducer. + +- `#315 `_: add ``--help`` and ``--version`` to helptox-quickstart. Thanks `@vlaci `_. + +- `#326 `_: Fix ``OSError`` 'Not a directory' when creating env on Jython 2.7.0. Thanks Nick Douma (`@LordGaav `_). + +- `#429 `_: Forward ``MSYSTEM`` by default on Windows. Thanks Marius Gedminas (`@mgedmin `_) for reporting this. + +- `#449 `_: add multi platform example to the docs. Thanks Aleks Bunin (`@sashkab `_) and `@rndr `_. + +- `#474 `_: Start using setuptools_scm for tag based versioning. + +- `#484 `_: Renamed `py.test` to `pytest` throughout the project. Thanks Slam (`@3lnc `_). + +- `#504 `_: With `-a`: do not show additional environments header if there are none. Thanks `@rndr `_. + +- `#515 `_: Don't require environment variables in test environments where they are not used. + Thanks André Caron (`@AndreLouisCaron `_). +- `#517 `_: Forward ``NUMBER_OF_PROCESSORS`` by default on Windows to fix ``multiprocessor.cpu_count()``. + Thanks André Caron (`@AndreLouisCaron `_). + +- `#518 `_: Forward `USERPROFILE` by default on Windows. Thanks André Caron (`@AndreLouisCaron `_). + +- `pull request 528 `_: Fix some of the warnings displayed by pytest 3.1.0. Thanks Bruno Oliveira (`@nicoddemus `_). + +- `pull request 547 `_: Add regression test for `#137 `_. Thanks Martin Andrysík (`@sifuraz `_). + +- `pull request 553 `_: Add an XFAIL test to reproduce upstream bug `#203 `_. Thanks + Bartolomé Sánchez Salado (`@bartsanchez `_). + +- `pull request 556 `_: Report more meaningful errors on why virtualenv creation failed. Thanks `@vlaci `_. + Also thanks to Igor Sadchenko (`@igor-sadchenko `_) for pointing out a problem with that PR + before it hit the masses ☺ + +- `pull request 575 `_: Add announcement doc to end all announcement docs + (using only ``CHANGELOG`` and Github issues since 2.5 already). + +- `pull request 580 `_: Do not ignore Sphinx warnings anymore. Thanks Bernát Gábor (`@gaborbernat `_). + +- `pull request 585 `_: Expand documentation to explain pass through of flags from deps to pip + (e.g. ``-rrequirements.txt``, ``-cconstraints.txt``). Thanks Alexander Loechel (`@loechel `_). + +- `pull request 588 `_: Run pytest wit xfail_strict and adapt affected tests. + +2.7.0 (2017-04-02) +------------------ + +- `pull request 450 `_: Stop after the first installdeps and first testenv create hooks + succeed. This changes the default behaviour of `tox_testenv_create` + and `tox_testenv_install_deps` to not execute other registered hooks when + the first hook returns a result that is not `None`. + Thanks Anthony Sottile (`@asottile `_). + +- `#271 `_ and `#464 `_: Improve environment information for users. + + New command line parameter: `-a` show **all** defined environments - + not just the ones defined in (or generated from) envlist. + + New verbosity settings for `-l` and `-a`: show user defined descriptions + of the environments. This also works for generated environments from factors + by concatenating factor descriptions into a complete description. + + Note that for backwards compatibility with scripts using the output of `-l` + it's output remains unchanged. + + Thanks Bernát Gábor (`@gaborbernat `_). + +- `#464 `_: Fix incorrect egg-info location for modified package_dir in setup.py. + Thanks Selim Belhaouane (`@selimb `_). + +- `#431 `_: Add 'LANGUAGE' to default passed environment variables. + Thanks Paweł Adamczak (`@pawelad `_). + +- `#455 `_: Add a Vagrantfile with a customized Arch Linux box for local testing. + Thanks Oliver Bestwalter (`@obestwalter `_). + +- `#454 `_: Revert `pull request 407 `_, empty commands is not treated as an error. + Thanks Anthony Sottile (`@asottile `_). + +- `#446 `_: (infrastructure) Travis CI tests for tox now also run on OS X now. + Thanks Jason R. Coombs (`@jaraco `_). + +2.6.0 (2017-02-04) +------------------ + +- add "alwayscopy" config option to instruct virtualenv to always copy + files instead of symlinking. Thanks Igor Duarte Cardoso (`@igordcard `_). + +- pass setenv variables to setup.py during a usedevelop install. + Thanks Eli Collins (`@eli-collins `_). + +- replace all references to testrun.org with readthedocs ones. + Thanks Oliver Bestwalter (`@obestwalter `_). + +- fix `#323 `_ by avoiding virtualenv14 is not used on py32 + (although we don't officially support py32). + Thanks Jason R. Coombs (`@jaraco `_). + +- add Python 3.6 to envlist and CI. + Thanks Andrii Soldatenko (`@andriisoldatenko `_). + +- fix glob resolution from TOX_TESTENV_PASSENV env variable + Thanks Allan Feldman (`@a-feld `_). + +2.5.0 (2016-11-16) +------------------ + +- slightly backward incompatible: fix `#310 `_: the {posargs} substitution + now properly preserves the tox command line positional arguments. Positional + arguments with spaces are now properly handled. + NOTE: if your tox invocation previously used extra quoting for positional arguments to + work around `#310 `_, you need to remove the quoting. Example: + tox -- "'some string'" # has to now be written simply as + tox -- "some string" + thanks holger krekel. You can set ``minversion = 2.5.0`` in the ``[tox]`` + section of ``tox.ini`` to make sure people using your tox.ini use the correct version. + +- fix `#359 `_: add COMSPEC to default passenv on windows. Thanks + `@anthrotype `_. + +- add support for py36 and py37 and add py36-dev and py37(nightly) to + travis builds of tox. Thanks John Vandenberg. + +- fix `#348 `_: add py2 and py3 as default environments pointing to + "python2" and "python3" basepython executables. Also fix `#347 `_ by + updating the list of default envs in the tox basic example. + Thanks Tobias McNulty. + +- make "-h" and "--help-ini" options work even if there is no tox.ini, + thanks holger krekel. + +- add {:} substitution, which is replaced with os-specific path + separator, thanks Lukasz Rogalski. + +- fix `#305 `_: ``downloadcache`` test env config is now ignored as pip-8 + does caching by default. Thanks holger krekel. + +- output from install command in verbose (-vv) mode is now printed to console instead of + being redirected to file, thanks Lukasz Rogalski + +- fix `#399 `_. Make sure {envtmpdir} is created if it doesn't exist at the + start of a testenvironment run. Thanks Manuel Jacob. + +- fix `#316 `_: Lack of commands key in ini file is now treated as an error. + Reported virtualenv status is 'nothing to do' instead of 'commands + succeeded', with relevant error message displayed. Thanks Lukasz Rogalski. + +2.4.1 (2016-10-12) +------------------ + +- fix `#380 `_: properly perform substitution again. Thanks Ian + Cordasco. + +2.4.0 (2016-10-12) +------------------ + +- remove PYTHONPATH from environment during the install phase because a + tox-run should not have hidden dependencies and the test commands will also + not see a PYTHONPATH. If this causes unforeseen problems it may be + reverted in a bugfix release. Thanks Jason R. Coombs. + +- fix `#352 `_: prevent a configuration where envdir==toxinidir and + refine docs to warn people about changing "envdir". Thanks Oliver Bestwalter and holger krekel. + +- fix `#375 `_, fix `#330 `_: warn against tox-setup.py integration as + "setup.py test" should really just test with the current interpreter. Thanks Ronny Pfannschmidt. + +- fix `#302 `_: allow cross-testenv substitution where we substitute + with ``{x,y}`` generative syntax. Thanks Andrew Pashkin. + +- fix `#212 `_: allow escaping curly brace chars "\{" and "\}" if you need the + chars "{" and "}" to appear in your commands or other ini values. + Thanks John Vandenberg. + +- addresses `#66 `_: add --workdir option to override where tox stores its ".tox" directory + and all of the virtualenv environment. Thanks Danring. + +- introduce per-venv list_dependencies_command which defaults + to "pip freeze" to obtain the list of installed packages. + Thanks Ted Shaw, Holger Krekel. + +- close `#66 `_: add documentation to jenkins page on how to avoid + "too long shebang" lines when calling pip from tox. Note that we + can not use "python -m pip install X" by default because the latter + adds the CWD and pip will think X is installed if it is there. + "pip install X" does not do that. + +- new list_dependencies_command to influence how tox determines + which dependencies are installed in a testenv. + +- (experimental) New feature: When a search for a config file fails, tox tries loading + setup.cfg with a section prefix of "tox". + +- fix `#275 `_: Introduce hooks ``tox_runtest_pre``` and + ``tox_runtest_post`` which run before and after the tests of a venv, + respectively. Thanks to Matthew Schinckel and itxaka serrano. + +- fix `#317 `_: evaluate minversion before tox config is parsed completely. + Thanks Sachi King for the PR. + +- added the "extras" environment option to specify the extras to use when doing the + sdist or develop install. Contributed by Alex Grönholm. + +- use pytest-catchlog instead of pytest-capturelog (latter is not + maintained, uses deprecated pytest API) + +2.3.2 (2016-02-11) +------------------ + +- fix `#314 `_: fix command invocation with .py scripts on windows. + +- fix `#279 `_: allow cross-section substitution when the value contains + posargs. Thanks Sachi King for the PR. + +2.3.1 (2015-12-14) +------------------ + +- fix `#294 `_: re-allow cross-section substitution for setenv. + +2.3.0 (2015-12-09) +------------------ + +- DEPRECATE use of "indexservers" in tox.ini. It complicates + the internal code and it is recommended to rather use the + devpi system for managing indexes for pip. + +- fix `#285 `_: make setenv processing fully lazy to fix regressions + of tox-2.2.X and so that we can now have testenv attributes like + "basepython" depend on environment variables that are set in + a setenv section. Thanks Nelfin for some tests and initial + work on a PR. + +- allow "#" in commands. This is slightly incompatible with commands + sections that used a comment after a "\" line continuation. + Thanks David Stanek for the PR. + +- fix `#289 `_: fix build_sphinx target, thanks Barry Warsaw. + +- fix `#252 `_: allow environment names with special characters. + Thanks Julien Castets for initial PR and patience. + +- introduce experimental tox_testenv_create(venv, action) and + tox_testenv_install_deps(venv, action) hooks to allow + plugins to do additional work on creation or installing + deps. These hooks are experimental mainly because of + the involved "venv" and session objects whose current public + API is not fully guranteed. + +- internal: push some optional object creation into tests because + tox core doesn't need it. + +2.2.1 (2015-12-09) +------------------ + +- fix bug where {envdir} substitution could not be used in setenv + if that env value is then used in {basepython}. Thanks Florian Bruhin. + +2.2.0 (2015-11-11) +------------------ + +- fix `#265 `_ and add LD_LIBRARY_PATH to passenv on linux by default + because otherwise the python interpreter might not start up in + certain configurations (redhat software collections). Thanks David Riddle. + +- fix `#246 `_: fix regression in config parsing by reordering + such that {envbindir} can be used again in tox.ini. Thanks Olli Walsh. + +- fix `#99 `_: the {env:...} substitution now properly uses environment + settings from the ``setenv`` section. Thanks Itxaka Serrano. + +- fix `#281 `_: make --force-dep work when urls are present in + dependency configs. Thanks Glyph Lefkowitz for reporting. + +- fix `#174 `_: add new ``ignore_outcome`` testenv attribute which + can be set to True in which case it will produce a warning instead + of an error on a failed testenv command outcome. + Thanks Rebecka Gulliksson for the PR. + +- fix `#280 `_: properly skip missing interpreter if + {envsitepackagesdir} is present in commands. Thanks BB:ceridwenv + + +2.1.1 (2015-06-23) +------------------ + +- fix platform skipping for detox + +- report skipped platforms as skips in the summary + +2.1.0 (2015-06-19) +------------------ + +- fix `#258 `_, fix `#248 `_, fix `#253 `_: for non-test commands + (installation, venv creation) we pass in the full invocation environment. + +- remove experimental --set-home option which was hardly used and + hackily implemented (if people want home-directory isolation we should + figure out a better way to do it, possibly through a plugin) + +- fix `#259 `_: passenv is now a line-list which allows to intersperse + comments. Thanks stefano-m. + +- allow envlist to be a multi-line list, to intersperse comments + and have long envlist settings split more naturally. Thanks Andre Caron. + +- introduce a TOX_TESTENV_PASSENV setting which is honored + when constructing the set of environment variables for test environments. + Thanks Marc Abramowitz for pushing in this direction. + +2.0.2 (2015-06-03) +------------------ + +- fix `#247 `_: tox now passes the LANG variable from the tox invocation + environment to the test environment by default. + +- add SYSTEMDRIVE into default passenv on windows to allow pip6 to work. + Thanks Michael Krause. + +2.0.1 (2015-05-13) +------------------ + +- fix wheel packaging to properly require argparse on py26. + +2.0.0 (2015-05-12) +------------------ + +- (new) introduce environment variable isolation: + tox now only passes the PATH and PIP_INDEX_URL variable from the tox + invocation environment to the test environment and on Windows + also ``SYSTEMROOT``, ``PATHEXT``, ``TEMP`` and ``TMP`` whereas + on unix additionally ``TMPDIR`` is passed. If you need to pass + through further environment variables you can use the new ``passenv`` setting, + a space-separated list of environment variable names. Each name + can make use of fnmatch-style glob patterns. All environment + variables which exist in the tox-invocation environment will be copied + to the test environment. + +- a new ``--help-ini`` option shows all possible testenv settings and + their defaults. + +- (new) introduce a way to specify on which platform a testenvironment is to + execute: the new per-venv "platform" setting allows to specify + a regular expression which is matched against sys.platform. + If platform is set and doesn't match the platform spec in the test + environment the test environment is ignored, no setup or tests are attempted. + +- (new) add per-venv "ignore_errors" setting, which defaults to False. + If ``True``, a non-zero exit code from one command will be ignored and + further commands will be executed (which was the default behavior in tox < + 2.0). If ``False`` (the default), then a non-zero exit code from one command + will abort execution of commands for that environment. + +- show and store in json the version dependency information for each venv + +- remove the long-deprecated "distribute" option as it has no effect these days. + +- fix `#233 `_: avoid hanging with tox-setuptools integration example. Thanks simonb. + +- fix `#120 `_: allow substitution for the commands section. Thanks + Volodymyr Vitvitski. + +- fix `#235 `_: fix AttributeError with --installpkg. Thanks + Volodymyr Vitvitski. + +- tox has now somewhat pep8 clean code, thanks to Volodymyr Vitvitski. + +- fix `#240 `_: allow to specify empty argument list without it being + rewritten to ".". Thanks Daniel Hahler. + +- introduce experimental (not much documented yet) plugin system + based on pytest's externalized "pluggy" system. + See tox/hookspecs.py for the current hooks. + +- introduce parser.add_testenv_attribute() to register an ini-variable + for testenv sections. Can be used from plugins through the + tox_add_option hook. + +- rename internal files -- tox offers no external API except for the + experimental plugin hooks, use tox internals at your own risk. + +- DEPRECATE distshare in documentation + +1.9.2 (2015-03-23) +------------------ + +- backout ability that --force-dep substitutes name/versions in + requirement files due to various issues. + This fixes `#228 `_, fixes `#230 `_, fixes `#231 `_ + which popped up with 1.9.1. + +1.9.1 (2015-03-23) +------------------ + +- use a file instead of a pipe for command output in "--result-json". + Fixes some termination issues with python2.6. + +- allow --force-dep to override dependencies in "-r" requirements + files. Thanks Sontek for the PR. + +- fix `#227 `_: use "-m virtualenv" instead of "-mvirtualenv" to make + it work with pyrun. Thanks Marc-Andre Lemburg. + + +1.9.0 (2015-02-24) +------------------ + +- fix `#193 `_: Remove ``--pre`` from the default ``install_command``; by + default tox will now only install final releases from PyPI for unpinned + dependencies. Use ``pip_pre = true`` in a testenv or the ``--pre`` + command-line option to restore the previous behavior. + +- fix `#199 `_: fill resultlog structure ahead of virtualenv creation + +- refine determination if we run from Jenkins, thanks Borge Lanes. + +- echo output to stdout when ``--report-json`` is used + +- fix `#11 `_: add a ``skip_install`` per-testenv setting which + prevents the installation of a package. Thanks Julian Krause. + +- fix `#124 `_: ignore command exit codes; when a command has a "-" prefix, + tox will ignore the exit code of that command + +- fix `#198 `_: fix broken envlist settings, e.g. {py26,py27}{-lint,} + +- fix `#191 `_: lessen factor-use checks + + +1.8.1 (2014-10-24) +------------------ + +- fix `#190 `_: allow setenv to be empty. + +- allow escaping curly braces with "\". Thanks Marc Abramowitz for the PR. + +- allow "." names in environment names such that "py27-django1.7" is a + valid environment name. Thanks Alex Gaynor and Alex Schepanovski. + +- report subprocess exit code when execution fails. Thanks Marius + Gedminas. + +1.8.0 (2014-09-24) +------------------ + +- new multi-dimensional configuration support. Many thanks to + Alexander Schepanovski for the complete PR with docs. + And to Mike Bayer and others for testing and feedback. + +- fix `#148 `_: remove "__PYVENV_LAUNCHER__" from os.environ when starting + subprocesses. Thanks Steven Myint. + +- fix `#152 `_: set VIRTUAL_ENV when running test commands, + thanks Florian Ludwig. + +- better report if we can't get version_info from an interpreter + executable. Thanks Floris Bruynooghe. + + +1.7.2 (2014-07-15) +------------------ + +- fix `#150 `_: parse {posargs} more like we used to do it pre 1.7.0. + The 1.7.0 behaviour broke a lot of OpenStack projects. + See PR85 and the issue discussions for (far) more details, hopefully + resulting in a more refined behaviour in the 1.8 series. + And thanks to Clark Boylan for the PR. + +- fix `#59 `_: add a config variable ``skip-missing-interpreters`` as well as + command line option ``--skip-missing-interpreters`` which won't fail the + build if Python interpreters listed in tox.ini are missing. Thanks + Alexandre Conrad for PR104. + +- fix `#164 `_: better traceback info in case of failing test commands. + Thanks Marc Abramowitz for PR92. + +- support optional env variable substitution, thanks Morgan Fainberg + for PR86. + +- limit python hashseed to 1024 on Windows to prevent possible + memory errors. Thanks March Schlaich for the PR90. + +1.7.1 (2014-03-28) +------------------ + +- fix `#162 `_: don't list python 2.5 as compatibiliy/supported + +- fix `#158 `_ and fix `#155 `_: windows/virtualenv properly works now: + call virtualenv through "python -m virtualenv" with the same + interpreter which invoked tox. Thanks Chris Withers, Ionel Maries Cristian. + +1.7.0 (2014-01-29) +------------------ + +- don't lookup "pip-script" anymore but rather just "pip" on windows + as this is a pip implementation detail and changed with pip-1.5. + It might mean that tox-1.7 is not able to install a different pip + version into a virtualenv anymore. + +- drop Python2.5 compatibility because it became too hard due + to the setuptools-2.0 dropping support. tox now has no + support for creating python2.5 based environments anymore + and all internal special-handling has been removed. + +- merged PR81: new option --force-dep which allows to + override tox.ini specified dependencies in setuptools-style. + For example "--force-dep 'django<1.6'" will make sure + that any environment using "django" as a dependency will + get the latest 1.5 release. Thanks Bruno Oliveria for + the complete PR. + +- merged PR125: tox now sets "PYTHONHASHSEED" to a random value + and offers a "--hashseed" option to repeat a test run with a specific seed. + You can also use --hashsheed=noset to instruct tox to leave the value + alone. Thanks Chris Jerdonek for all the work behind this. + +- fix `#132 `_: removing zip_safe setting (so it defaults to false) + to allow installation of tox + via easy_install/eggs. Thanks Jenisys. + +- fix `#126 `_: depend on virtualenv>=1.11.2 so that we can rely + (hopefully) on a pip version which supports --pre. (tox by default + uses to --pre). also merged in PR84 so that we now call "virtualenv" + directly instead of looking up interpreters. Thanks Ionel Maries Cristian. + This also fixes `#140 `_. + +- fix `#130 `_: you can now set install_command=easy_install {opts} {packages} + and expect it to work for repeated tox runs (previously it only worked + when always recreating). Thanks jenisys for precise reporting. + +- fix `#129 `_: tox now uses Popen(..., universal_newlines=True) to force + creation of unicode stdout/stderr streams. fixes a problem on specific + platform configs when creating virtualenvs with Python3.3. Thanks + Jorgen Schäfer or investigation and solution sketch. + +- fix `#128 `_: enable full substitution in install_command, + thanks for the PR to Ronald Evers + +- rework and simplify "commands" parsing and in particular posargs + substitutions to avoid various win32/posix related quoting issues. + +- make sure that the --installpkg option trumps any usedevelop settings + in tox.ini or + +- introduce --no-network to tox's own test suite to skip tests + requiring networks + +- introduce --sitepackages to force sitepackages=True in all + environments. + +- fix `#105 `_ -- don't depend on an existing HOME directory from tox tests. + +1.6.1 (2013-09-04) +------------------ + +- fix `#119 `_: {envsitepackagesdir} is now correctly computed and has + a better test to prevent regression. + +- fix `#116 `_: make 1.6 introduced behaviour of changing to a + per-env HOME directory during install activities dependent + on "--set-home" for now. Should re-establish the old behaviour + when no option is given. + +- fix `#118 `_: correctly have two tests use realpath(). Thanks Barry + Warsaw. + +- fix test runs on environments without a home directory + (in this case we use toxinidir as the homedir) + +- fix `#117 `_: python2.5 fix: don't use ``--insecure`` option because + its very existence depends on presence of "ssl". If you + want to support python2.5/pip1.3.1 based test environments you need + to install ssl and/or use PIP_INSECURE=1 through ``setenv``. section. + +- fix `#102 `_: change to {toxinidir} when installing dependencies. + this allows to use relative path like in "-rrequirements.txt". + +1.6.0 (2013-08-15) +------------------ + +- fix `#35 `_: add new EXPERIMENTAL "install_command" testenv-option to + configure the installation command with options for dep/pkg install. + Thanks Carl Meyer for the PR and docs. + +- fix `#91 `_: python2.5 support by vendoring the virtualenv-1.9.1 + script and forcing pip<1.4. Also the default [py25] environment + modifies the default installer_command (new config option) + to use pip without the "--pre" option which was introduced + with pip-1.4 and is now required if you want to install non-stable + releases. (tox defaults to install with "--pre" everywhere). + +- during installation of dependencies HOME is now set to a pseudo + location ({envtmpdir}/pseudo-home). If an index url was specified + a .pydistutils.cfg file will be written with an index_url setting + so that packages defining ``setup_requires`` dependencies will not + silently use your HOME-directory settings or https://pypi.python.org/pypi. + +- fix `#1 `_: empty setup files are properly detected, thanks Anthon van + der Neuth + +- remove toxbootstrap.py for now because it is broken. + +- fix `#109 `_ and fix `#111 `_: multiple "-e" options are now combined + (previously the last one would win). Thanks Anthon van der Neut. + +- add --result-json option to write out detailed per-venv information + into a json report file to be used by upstream tools. + +- add new config options ``usedevelop`` and ``skipsdist`` as well as a + command line option ``--develop`` to install the package-under-test in develop mode. + thanks Monty Tailor for the PR. + +- always unset PYTHONDONTWRITEBYTE because newer setuptools doesn't like it + +- if a HOMEDIR cannot be determined, use the toxinidir. + +- refactor interpreter information detection to live in new + tox/interpreters.py file, tests in tests/test_interpreters.py. + +1.5.0 (2013-06-22) +------------------ + +- fix `#104 `_: use setuptools by default, instead of distribute, + now that setuptools has distribute merged. + +- make sure test commands are searched first in the virtualenv + +- re-fix `#2 `_ - add whitelist_externals to be used in ``[testenv*]`` + sections, allowing to avoid warnings for commands such as ``make``, + used from the commands value. + +- fix `#97 `_ - allow substitutions to reference from other sections + (thanks Krisztian Fekete) + +- fix `#92 `_ - fix {envsitepackagesdir} to actually work again + +- show (test) command that is being executed, thanks + Lukasz Balcerzak + +- re-license tox to MIT license + +- depend on virtualenv-1.9.1 + +- rename README.txt to README.rst to make bitbucket happier + + +1.4.3 (2013-02-28) +------------------ + +- use pip-script.py instead of pip.exe on win32 to avoid the lock exe + file on execution issue (thanks Philip Thiem) + +- introduce -l|--listenv option to list configured environments + (thanks Lukasz Balcerzak) + +- fix downloadcache determination to work according to docs: Only + make pip use a download cache if PIP_DOWNLOAD_CACHE or a + downloadcache=PATH testenv setting is present. (The ENV setting + takes precedence) + +- fix `#84 `_ - pypy on windows creates a bin not a scripts venv directory + (thanks Lukasz Balcerzak) + +- experimentally introduce --installpkg=PATH option to install a package + rather than create/install an sdist package. This will still require + and use tox.ini and tests from the current working dir (and not from the + remote package). + +- substitute {envsitepackagesdir} with the package installation + directory (closes `#72 `_) (thanks g2p) + +- issue `#70 `_ remove PYTHONDONTWRITEBYTECODE workaround now that + virtualenv behaves properly (thanks g2p) + +- merged tox-quickstart command, contributed by Marc Abramowitz, which + generates a default tox.ini after asking a few questions + +- fix `#48 `_ - win32 detection of pypy and other interpreters that are on PATH + (thanks Gustavo Picon) + +- fix grouping of index servers, it is now done by name instead of + indexserver url, allowing to use it to separate dependencies + into groups even if using the same default indexserver. + +- look for "tox.ini" files in parent dirs of current dir (closes `#34 `_) + +- the "py" environment now by default uses the current interpreter + (sys.executable) make tox' own setup.py test execute tests with it + (closes `#46 `_) + +- change tests to not rely on os.path.expanduser (closes `#60 `_), + also make mock session return args[1:] for more precise checking (closes `#61 `_) + thanks to Barry Warsaw for both. + +1.4.2 (2012-07-20) +------------------ + +- fix some tests which fail if /tmp is a symlink to some other place +- "python setup.py test" now runs tox tests via tox :) + also added an example on how to do it for your project. + +1.4.1 (2012-07-03) +------------------ + +- fix `#41 `_ better quoting on windows - you can now use "<" and ">" in + deps specifications, thanks Chris Withers for reporting + +1.4 (2012-06-13) +---------------- + +- fix `#26 `_ - no warnings on absolute or relative specified paths for commands +- fix `#33 `_ - commentchars are ignored in key-value settings allowing + for specifying commands like: python -c "import sys ; print sys" + which would formerly raise irritating errors because the ";" + was considered a comment +- tweak and improve reporting +- refactor reporting and virtualenv manipulation + to be more accessible from 3rd party tools +- support value substitution from other sections + with the {[section]key} syntax +- fix `#29 `_ - correctly point to pytest explanation + for importing modules fully qualified +- fix `#32 `_ - use --system-site-packages and don't pass --no-site-packages +- add python3.3 to the default env list, so early adopters can test +- drop python2.4 support (you can still have your tests run on +- fix the links/checkout howtos in the docs + python-2.4, just tox itself requires 2.5 or higher. + +1.3 2011-12-21 +-------------- + +- fix: allow to specify wildcard filesystem paths when + specifying dependencies such that tox searches for + the highest version + +- fix issue `#21 `_: clear PIP_REQUIRES_VIRTUALENV which avoids + pip installing to the wrong environment, thanks to bb's streeter + +- make the install step honour a testenv's setenv setting + (thanks Ralf Schmitt) + + +1.2 2011-11-10 +-------------- + +- remove the virtualenv.py that was distributed with tox and depend + on >=virtualenv-1.6.4 (possible now since the latter fixes a few bugs + that the inlining tried to work around) +- fix `#10 `_: work around UnicodeDecodeError when invoking pip (thanks + Marc Abramowitz) +- fix a problem with parsing {posargs} in tox commands (spotted by goodwill) +- fix the warning check for commands to be installed in testenvironment + (thanks Michael Foord for reporting) + +1.1 (2011-07-08) +---------------- + +- fix `#5 `_ - don't require argparse for python versions that have it +- fix `#6 `_ - recreate virtualenv if installing dependencies failed +- fix `#3 `_ - fix example on frontpage +- fix `#2 `_ - warn if a test command does not come from the test + environment +- fixed/enhanced: except for initial install always call "-U + --no-deps" for installing the sdist package to ensure that a package + gets upgraded even if its version number did not change. (reported on + TIP mailing list and IRC) +- inline virtualenv.py (1.6.1) script to avoid a number of issues, + particularly failing to install python3 environments from a python2 + virtualenv installation. +- rework and enhance docs for display on readthedocs.org + +1.0 +--- + +- move repository and toxbootstrap links to https://bitbucket.org/hpk42/tox +- fix `#7 `_: introduce a "minversion" directive such that tox + bails out if it does not have the correct version. +- fix `#24 `_: introduce a way to set environment variables for + for test commands (thanks Chris Rose) +- fix `#22 `_: require virtualenv-1.6.1, obsoleting virtualenv5 (thanks Jannis Leidel) + and making things work with pypy-1.5 and python3 more seamlessly +- toxbootstrap.py (used by jenkins build slaves) now follows the latest release of virtualenv +- fix `#20 `_: document format of URLs for specifying dependencies +- fix `#19 `_: substitute Hudson for Jenkins everywhere following the renaming + of the project. NOTE: if you used the special [tox:hudson] + section it will now need to be named [tox:jenkins]. +- fix issue 23 / apply some ReST fixes +- change the positional argument specifier to use {posargs:} syntax and + fix issues `#15 `_ and `#10 `_ by refining the argument parsing method (Chris Rose) +- remove use of inipkg lazy importing logic - + the namespace/imports are anyway very small with tox. +- fix a fspath related assertion to work with debian installs which uses + symlinks +- show path of the underlying virtualenv invocation and bootstrap + virtualenv.py into a working subdir +- added a CONTRIBUTORS file + +0.9 +--- + +- fix pip-installation mixups by always unsetting PIP_RESPECT_VIRTUALENV + (thanks Armin Ronacher) +- `#1 `_: Add a toxbootstrap.py script for tox, thanks to Sridhar + Ratnakumar +- added support for working with different and multiple PyPI indexservers. +- new option: -r|--recreate to force recreation of virtualenv +- depend on py>=1.4.0 which does not contain or install the py.test + anymore which is now a separate distribution "pytest". +- show logfile content if there is an error (makes CI output + more readable) + +0.8 +--- + +- work around a virtualenv limitation which crashes if + PYTHONDONTWRITEBYTECODE is set. +- run pip/easy installs from the environment log directory, avoids + naming clashes between env names and dependencies (thanks ronny) +- require a more recent version of py lib +- refactor and refine config detection to work from a single file + and to detect the case where a python installation overwrote + an old one and resulted in a new executable. This invalidates + the existing virtualenvironment now. +- change all internal source to strip trailing whitespaces + +0.7 +--- + +- use virtualenv5 (my own fork of virtualenv3) for now to create python3 + environments, fixes a couple of issues and makes tox more likely to + work with Python3 (on non-windows environments) + +- add ``sitepackages`` option for testenv sections so that environments + can be created with access to globals (default is not to have access, + i.e. create environments with ``--no-site-packages``. + +- addressing `#4 `_: always prepend venv-path to PATH variable when calling subprocesses + +- fix `#2 `_: exit with proper non-zero return code if there were + errors or test failures. + +- added unittest2 examples contributed by Michael Foord + +- only allow 'True' or 'False' for boolean config values + (lowercase / uppercase is irrelevant) + +- recreate virtualenv on changed configurations + +0.6 +--- + +- fix OSX related bugs that could cause the caller's environment to get + screwed (sorry). tox was using the same file as virtualenv for tracking + the Python executable dependency and there also was confusion wrt links. + this should be fixed now. + +- fix long description, thanks Michael Foord + +0.5 +--- + +- initial release + + diff --git a/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/RECORD b/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..a566e94bedee1a85fc817a9c198934d98d5b5527 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/RECORD @@ -0,0 +1,30 @@ +../../../bin/tox,sha256=ZxDE1-sjSMbs0xCe1Z8UMEUl5iroHAYMW0WTyZWMaWw,262 +../../../bin/tox-quickstart,sha256=jYoKl8FcWiTILH3_Sk4XeBanQJAvlMz4d0Gy8FRRqyw,258 +tox-3.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +tox-3.0.0.dist-info/METADATA,sha256=bPvdTmp6Zy7FXpZhQsonshulhUpzB_E2Eh6z-c8jv9Q,52974 +tox-3.0.0.dist-info/RECORD,, +tox-3.0.0.dist-info/WHEEL,sha256=J3CsTk7Mf2JNUyhImI-mjX-fmI4oDjyiXgWT4qgZiCE,110 +tox-3.0.0.dist-info/entry_points.txt,sha256=eNABUv11dRrKfLmoXlPlUvJAidRV5wqWXLNaXVP_y-s,84 +tox-3.0.0.dist-info/top_level.txt,sha256=j-NhlvLEu1pj85DD4uEVKjq6vNP0HBKVuG93PJxTxjI,4 +tox/__init__.py,sha256=Xp9DCkvdEbModsiO3dtCHPBHBh0Do0X1HvyqXkewf10,3053 +tox/__main__.py,sha256=NU4v5w3efvkX4c5QCZNaGsa2GmLzpnUHOaYm39PD4Dk,76 +tox/__pycache__/__init__.cpython-38.pyc,, +tox/__pycache__/__main__.cpython-38.pyc,, +tox/__pycache__/_pytestplugin.cpython-38.pyc,, +tox/__pycache__/_quickstart.cpython-38.pyc,, +tox/__pycache__/_verlib.cpython-38.pyc,, +tox/__pycache__/config.cpython-38.pyc,, +tox/__pycache__/hookspecs.cpython-38.pyc,, +tox/__pycache__/interpreters.cpython-38.pyc,, +tox/__pycache__/result.cpython-38.pyc,, +tox/__pycache__/session.cpython-38.pyc,, +tox/__pycache__/venv.cpython-38.pyc,, +tox/_pytestplugin.py,sha256=FQV8k7X9ZzHcdeD3gBbSmOBY_MwIn9A0hnlZVJI4sfQ,10939 +tox/_quickstart.py,sha256=fTlxcGX_kkplvSabdRT2xacNxATxHfofVmhT3-CTN9E,8761 +tox/_verlib.py,sha256=Cy6Ih_QDy2TEEXAgploY7X1hHUzR0fZsnxKPfRu8348,8156 +tox/config.py,sha256=gPpHQrtGbXsdWVnEllo7vd52ueoIn2AC2A7mCicpRUU,51060 +tox/hookspecs.py,sha256=vDO5YnOvu0wG4_DBBpfc63L1b7Nag2yV8QLbdSb4SzA,3838 +tox/interpreters.py,sha256=xLVBu7YhnvQxXzsYweoOqLCTSevcV7DOX4ZDWTFV_tw,5734 +tox/result.py,sha256=PqmuqunBsZct6bgSbp_K0dnBzD3VMXQ3unSHH-k0Csw,2385 +tox/session.py,sha256=gph6F3o0sTw9x9V8Md7819b-dr-Z7P82o5I6nem8C0c,28341 +tox/venv.py,sha256=ndDCUsQPkQwfo5sQ5DwCt-h65X3GGYk9Y3U1apxNQY8,17525 diff --git a/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/WHEEL b/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..f21b51cd8af46f70e8fc3a7bd678e2db8baf104c --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.31.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/entry_points.txt b/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..aa418ad4a6b03b67b2af8c93f1944a5588b5fd8d --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/entry_points.txt @@ -0,0 +1,4 @@ +[console_scripts] +tox = tox.session:run_main +tox-quickstart = tox._quickstart:main + diff --git a/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..053148f848642cb6c79ecb61968f0a9d3fea8bb4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox-3.0.0.dist-info/top_level.txt @@ -0,0 +1 @@ +tox diff --git a/robot/lib/python3.8/site-packages/tox/__init__.py b/robot/lib/python3.8/site-packages/tox/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fd2b86c89ad087392d072270b461e2cc1931f602 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox/__init__.py @@ -0,0 +1,89 @@ +import os +import signal + +from pkg_resources import DistributionNotFound +from pkg_resources import get_distribution + +from .hookspecs import hookimpl +from .hookspecs import hookspec + +try: + _full_version = get_distribution(__name__).version + __version__ = _full_version.split('+', 1)[0] +except DistributionNotFound: + __version__ = '0.0.0.dev0' + + +# separate function because pytest-mock `spy` does not work on Exceptions +# can use neither a class method nor a static because of +# https://bugs.python.org/issue23078 +# even a normal method failed with +# TypeError: descriptor '__getattribute__' requires a 'BaseException' object but received a 'type' +def _exit_code_str(exception_name, command, exit_code): + """ string representation for an InvocationError, with exit code """ + str_ = "%s for command %s" % (exception_name, command) + if exit_code is not None: + str_ += " (exited with code %d)" % (exit_code) + if (os.name == 'posix') and (exit_code > 128): + signals = {number: name + for name, number in vars(signal).items() + if name.startswith("SIG")} + number = exit_code - 128 + name = signals.get(number) + if name: + str_ += ("\nNote: this might indicate a fatal error signal " + "({} - 128 = {}: {})".format(number+128, number, name)) + return str_ + + +class exception: + class Error(Exception): + def __str__(self): + return "%s: %s" % (self.__class__.__name__, self.args[0]) + + class MissingSubstitution(Error): + FLAG = 'TOX_MISSING_SUBSTITUTION' + """placeholder for debugging configurations""" + + def __init__(self, name): + self.name = name + + class ConfigError(Error): + """ error in tox configuration. """ + + class UnsupportedInterpreter(Error): + """signals an unsupported Interpreter""" + + class InterpreterNotFound(Error): + """signals that an interpreter could not be found""" + + class InvocationError(Error): + """ an error while invoking a script. """ + def __init__(self, command, exit_code=None): + super(exception.Error, self).__init__(command, exit_code) + self.command = command + self.exit_code = exit_code + + def __str__(self): + return _exit_code_str(self.__class__.__name__, self.command, self.exit_code) + + class MissingFile(Error): + """ an error while invoking a script. """ + + class MissingDirectory(Error): + """ a directory did not exist. """ + + class MissingDependency(Error): + """ a dependency could not be found or determined. """ + + class MinVersionError(Error): + """ the installed tox version is lower than requested minversion. """ + + def __init__(self, message): + self.message = message + super(exception.MinVersionError, self).__init__(message) + + +from .session import run_main as cmdline # noqa + +__all__ = ('hookspec', 'hookimpl', 'cmdline', 'exception', '__version__') diff --git a/robot/lib/python3.8/site-packages/tox/__main__.py b/robot/lib/python3.8/site-packages/tox/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..aadd3d2d7a895ee79c493277f882c2983d7a9f55 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox/__main__.py @@ -0,0 +1,4 @@ +from tox.session import run_main + +if __name__ == '__main__': + run_main() diff --git a/robot/lib/python3.8/site-packages/tox/_pytestplugin.py b/robot/lib/python3.8/site-packages/tox/_pytestplugin.py new file mode 100644 index 0000000000000000000000000000000000000000..4baa459221661f540e088a635e04864bcaaec6a6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox/_pytestplugin.py @@ -0,0 +1,366 @@ +from __future__ import print_function +from __future__ import unicode_literals + +import os +import textwrap +import time +from fnmatch import fnmatch + +import py +import pytest +import six + +import tox +from .config import parseconfig +from .result import ResultLog +from .session import main +from .venv import VirtualEnv + + +def pytest_configure(): + if 'TOXENV' in os.environ: + del os.environ['TOXENV'] + if 'HUDSON_URL' in os.environ: + del os.environ['HUDSON_URL'] + + +def pytest_addoption(parser): + parser.addoption("--no-network", action="store_true", + dest="no_network", + help="don't run tests requiring network") + + +def pytest_report_header(): + return "tox comes from: {}".format(repr(tox.__file__)) + + +@pytest.fixture +def newconfig(request, tmpdir): + def newconfig(args, source=None, plugins=()): + if source is None: + source = args + args = [] + s = textwrap.dedent(source) + p = tmpdir.join("tox.ini") + p.write(s) + old = tmpdir.chdir() + try: + return parseconfig(args, plugins=plugins) + finally: + old.chdir() + + return newconfig + + +@pytest.fixture +def cmd(request, capfd, monkeypatch): + if request.config.option.no_network: + pytest.skip("--no-network was specified, test cannot run") + request.addfinalizer(py.path.local().chdir) + + def run(*argv): + key = str(b'PYTHONPATH') + python_paths = (i for i in (str(os.getcwd()), os.getenv(key)) if i) + monkeypatch.setenv(key, os.pathsep.join(python_paths)) + with RunResult(capfd, argv) as result: + try: + main([str(x) for x in argv]) + assert False # this should always exist with SystemExit + except SystemExit as exception: + result.ret = exception.code + except OSError as e: + result.ret = e.errno + return result + + yield run + + +class RunResult: + def __init__(self, capfd, args): + self._capfd = capfd + self.args = args + self.ret = None + self.duration = None + self.out = None + self.err = None + + def __enter__(self): + self._start = time.time() + # noinspection PyProtectedMember + self._capfd._start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.duration = time.time() - self._start + self.out, self.err = self._capfd.readouterr() + + @property + def outlines(self): + return self.out.splitlines() + + def __repr__(self): + return 'RunResult(ret={}, args={}, out=\n{}\n, err=\n{})'.format( + self.ret, ' '.join(str(i) for i in self.args), self.out, self.err) + + +class ReportExpectMock: + def __init__(self, session): + self._calls = [] + self._index = -1 + self.session = session + + def clear(self): + self._calls[:] = [] + + def __getattr__(self, name): + if name[0] == "_": + raise AttributeError(name) + elif name == 'verbosity': + # FIXME: special case for property on Reporter class, may it be generalized? + return 0 + + def generic_report(*args, **kwargs): + self._calls.append((name,) + args) + print("%s" % (self._calls[-1],)) + + return generic_report + + def getnext(self, cat): + __tracebackhide__ = True + newindex = self._index + 1 + while newindex < len(self._calls): + call = self._calls[newindex] + lcat = call[0] + if fnmatch(lcat, cat): + self._index = newindex + return call + newindex += 1 + raise LookupError( + "looking for %r, no reports found at >=%d in %r" % + (cat, self._index + 1, self._calls)) + + def expect(self, cat, messagepattern="*", invert=False): + __tracebackhide__ = True + if not messagepattern.startswith("*"): + messagepattern = "*" + messagepattern + while self._index < len(self._calls): + try: + call = self.getnext(cat) + except LookupError: + break + for lmsg in call[1:]: + lmsg = str(lmsg).replace("\n", " ") + if fnmatch(lmsg, messagepattern): + if invert: + raise AssertionError("found %s(%r), didn't expect it" % + (cat, messagepattern)) + return + if not invert: + raise AssertionError( + "looking for %s(%r), no reports found at >=%d in %r" % + (cat, messagepattern, self._index + 1, self._calls)) + + def not_expect(self, cat, messagepattern="*"): + return self.expect(cat, messagepattern, invert=True) + + +class pcallMock: + def __init__(self, args, cwd, env, stdout, stderr, shell): + self.arg0 = args[0] + self.args = args[1:] + self.cwd = cwd + self.env = env + self.stdout = stdout + self.stderr = stderr + self.shell = shell + + def communicate(self): + return "", "" + + def wait(self): + pass + + +@pytest.fixture +def mocksession(request): + from tox.session import Session + + class MockSession(Session): + def __init__(self): + self._clearmocks() + self.config = request.getfixturevalue("newconfig")([], "") + self.resultlog = ResultLog() + self._actions = [] + + def getenv(self, name): + return VirtualEnv(self.config.envconfigs[name], session=self) + + def _clearmocks(self): + self._pcalls = [] + self._spec2pkg = {} + self.report = ReportExpectMock(self) + + def make_emptydir(self, path): + pass + + def popen(self, args, cwd, shell=None, + universal_newlines=False, + stdout=None, stderr=None, env=None): + pm = pcallMock(args, cwd, env, stdout, stderr, shell) + self._pcalls.append(pm) + return pm + + return MockSession() + + +@pytest.fixture +def newmocksession(request): + mocksession = request.getfixturevalue("mocksession") + newconfig = request.getfixturevalue("newconfig") + + def newmocksession(args, source, plugins=()): + mocksession.config = newconfig(args, source, plugins=plugins) + return mocksession + + return newmocksession + + +def getdecoded(out): + try: + return out.decode("utf-8") + except UnicodeDecodeError: + return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % ( + py.io.saferepr(out),) + + +@pytest.fixture +def initproj(request, tmpdir): + """Create a factory function for creating example projects + + Constructed folder/file hierarchy examples: + + with `src_root` other than `.`: + + tmpdir/ + name/ # base + src_root/ # src_root + name/ # package_dir + __init__.py + name.egg-info/ # created later on package build + setup.py + + with `src_root` given as `.`: + + tmpdir/ + name/ # base, src_root + name/ # package_dir + __init__.py + name.egg-info/ # created later on package build + setup.py + + """ + + def initproj(nameversion, filedefs=None, src_root="."): + if filedefs is None: + filedefs = {} + if not src_root: + src_root = '.' + if isinstance(nameversion, six.string_types): + parts = nameversion.split(str("-")) + if len(parts) == 1: + parts.append("0.1") + name, version = parts + else: + name, version = nameversion + + base = tmpdir.join(name) + src_root_path = _path_join(base, src_root) + assert base == src_root_path or src_root_path.relto(base), ( + '`src_root` must be the constructed project folder or its direct ' + 'or indirect subfolder') + + base.ensure(dir=1) + create_files(base, filedefs) + + if not _filedefs_contains(base, filedefs, 'setup.py'): + create_files(base, {'setup.py': ''' + from setuptools import setup, find_packages + setup( + name='%(name)s', + description='%(name)s project', + version='%(version)s', + license='MIT', + platforms=['unix', 'win32'], + packages=find_packages('%(src_root)s'), + package_dir={'':'%(src_root)s'}, + ) + ''' % locals()}) + + if not _filedefs_contains(base, filedefs, src_root_path.join(name)): + create_files(src_root_path, { + name: {'__init__.py': '__version__ = %r' % version} + }) + + manifestlines = ["include %s" % p.relto(base) + for p in base.visit(lambda x: x.check(file=1))] + create_files(base, {"MANIFEST.in": "\n".join(manifestlines)}) + + print("created project in %s" % (base,)) + base.chdir() + return base + + return initproj + + +def _path_parts(path): + path = path and str(path) # py.path.local support + parts = [] + while path: + folder, name = os.path.split(path) + if folder == path: # root folder + folder, name = name, folder + if name: + parts.append(name) + path = folder + parts.reverse() + return parts + + +def _path_join(base, *args): + # workaround for a py.path.local bug on Windows (`path.join('/x', abs=1)` + # should be py.path.local('X:\\x') where `X` is the current drive, when in + # fact it comes out as py.path.local('\\x')) + return py.path.local(base.join(*args, abs=1)) + + +def _filedefs_contains(base, filedefs, path): + """ + whether `filedefs` defines a file/folder with the given `path` + + `path`, if relative, will be interpreted relative to the `base` folder, and + whether relative or not, must refer to either the `base` folder or one of + its direct or indirect children. The base folder itself is considered + created if the filedefs structure is not empty. + + """ + unknown = object() + base = py.path.local(base) + path = _path_join(base, path) + + path_rel_parts = _path_parts(path.relto(base)) + for part in path_rel_parts: + if not isinstance(filedefs, dict): + return False + filedefs = filedefs.get(part, unknown) + if filedefs is unknown: + return False + return path_rel_parts or path == base and filedefs + + +def create_files(base, filedefs): + for key, value in filedefs.items(): + if isinstance(value, dict): + create_files(base.ensure(key, dir=1), value) + elif isinstance(value, six.string_types): + s = textwrap.dedent(value) + base.join(key).write(s) diff --git a/robot/lib/python3.8/site-packages/tox/_quickstart.py b/robot/lib/python3.8/site-packages/tox/_quickstart.py new file mode 100644 index 0000000000000000000000000000000000000000..d685f12aa9b5ae2d07c0e577fc550be4ad6cdcdc --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox/_quickstart.py @@ -0,0 +1,296 @@ +# -*- coding: utf-8 -*- +""" + tox._quickstart + ~~~~~~~~~~~~~~~~~ + + Command-line script to quickly setup tox.ini for a Python project + + This file was heavily inspired by and uses code from ``sphinx-quickstart`` + in the BSD-licensed `Sphinx project`_. + + .. Sphinx project_: http://sphinx.pocoo.org/ + + License for Sphinx + ================== + + Copyright (c) 2007-2011 by the Sphinx team (see AUTHORS file). + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" +import argparse +import sys +from codecs import open +from os import path + +TERM_ENCODING = getattr(sys.stdin, 'encoding', None) + +from tox import __version__ # noqa #E402 module level import not at top of file + +# function to get input from terminal -- overridden by the test suite +try: + # this raw_input is not converted by 2to3 + term_input = raw_input +except NameError: + term_input = input + +all_envs = ['py27', 'py34', 'py35', 'py36', 'pypy', 'jython'] + +PROMPT_PREFIX = '> ' + +QUICKSTART_CONF = '''\ +# tox (https://tox.readthedocs.io/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = %(envlist)s + +[testenv] +commands = %(commands)s +deps = %(deps)s +''' + + +class ValidationError(Exception): + """Raised for validation errors.""" + + +def nonempty(x): + if not x: + raise ValidationError("Please enter some text.") + return x + + +def choice(*l): + def val(x): + if x not in l: + raise ValidationError('Please enter one of %s.' % ', '.join(l)) + return x + + return val + + +def boolean(x): + if x.upper() not in ('Y', 'YES', 'N', 'NO'): + raise ValidationError("Please enter either 'y' or 'n'.") + return x.upper() in ('Y', 'YES') + + +def suffix(x): + if not (x[0:1] == '.' and len(x) > 1): + raise ValidationError("Please enter a file suffix, " + "e.g. '.rst' or '.txt'.") + return x + + +def ok(x): + return x + + +def do_prompt(d, key, text, default=None, validator=nonempty): + while True: + if default: + prompt = PROMPT_PREFIX + '%s [%s]: ' % (text, default) + else: + prompt = PROMPT_PREFIX + text + ': ' + x = term_input(prompt) + if default and not x: + x = default + if sys.version_info < (3,) and not isinstance(x, unicode): # noqa + # for Python 2.x, try to get a Unicode string out of it + if x.decode('ascii', 'replace').encode('ascii', 'replace') != x: + if TERM_ENCODING: + x = x.decode(TERM_ENCODING) + else: + print('* Note: non-ASCII characters entered ' + 'and terminal encoding unknown -- assuming ' + 'UTF-8 or Latin-1.') + try: + x = x.decode('utf-8') + except UnicodeDecodeError: + x = x.decode('latin1') + try: + x = validator(x) + except ValidationError: + err = sys.exc_info()[1] + print('* ' + str(err)) + continue + break + d[key] = x + + +def ask_user(d): + """Ask the user for quickstart values missing from *d*. + + """ + + print('Welcome to the tox %s quickstart utility.' % __version__) + print(''' +This utility will ask you a few questions and then generate a simple tox.ini +file to help get you started using tox. + +Please enter values for the following settings (just press Enter to +accept a default value, if one is given in brackets).''') + + sys.stdout.write('\n') + + print(''' +What Python versions do you want to test against? Choices: + [1] py27 + [2] py27, py36 + [3] (All versions) %s + [4] Choose each one-by-one''' % ', '.join(all_envs)) + do_prompt(d, 'canned_pyenvs', 'Enter the number of your choice', + '3', choice('1', '2', '3', '4')) + + if d['canned_pyenvs'] == '1': + d['py27'] = True + elif d['canned_pyenvs'] == '2': + for pyenv in ('py27', 'py36'): + d[pyenv] = True + elif d['canned_pyenvs'] == '3': + for pyenv in all_envs: + d[pyenv] = True + elif d['canned_pyenvs'] == '4': + for pyenv in all_envs: + if pyenv not in d: + do_prompt(d, pyenv, 'Test your project with %s (Y/n)' % pyenv, 'Y', boolean) + + print(''' +What command should be used to test your project -- examples: + - pytest + - python setup.py test + - nosetests package.module + - trial package.module''') + do_prompt(d, 'commands', 'Command to run to test project', '{envpython} setup.py test') + + default_deps = ' ' + if any(c in d['commands'] for c in ['pytest', 'py.test']): + default_deps = 'pytest' + if 'nosetests' in d['commands']: + default_deps = 'nose' + if 'trial' in d['commands']: + default_deps = 'twisted' + + print(''' +What extra dependencies do your tests have?''') + do_prompt(d, 'deps', 'Comma-separated list of dependencies', default_deps) + + +def process_input(d): + d['envlist'] = ', '.join([env for env in all_envs if d.get(env) is True]) + d['deps'] = '\n' + '\n'.join([ + ' %s' % dep.strip() + for dep in d['deps'].split(',')]) + + return d + + +def rtrim_right(text): + lines = [] + for line in text.split("\n"): + lines.append(line.rstrip()) + return "\n".join(lines) + + +def generate(d, overwrite=True, silent=False): + """Generate project based on values in *d*.""" + + conf_text = QUICKSTART_CONF % d + conf_text = rtrim_right(conf_text) + + def write_file(fpath, mode, content): + print('Creating file %s.' % fpath) + try: + with open(fpath, mode, encoding='utf-8') as f: + f.write(content) + except IOError: + print('Error writing file.') + raise + + sys.stdout.write('\n') + + fpath = path.join(d.get('path', ''), 'tox.ini') + + if path.isfile(fpath) and not overwrite: + print('File %s already exists.' % fpath) + do_prompt( + d, + 'fpath', + 'Alternative path to write tox.ini contents to', + path.join(d.get('path', ''), 'tox-generated.ini')) + fpath = d['fpath'] + + write_file(fpath, 'w', conf_text) + + if silent: + return + sys.stdout.write('\n') + print('Finished: A tox.ini file has been created. For information on this file, ' + 'see https://tox.readthedocs.io/en/latest/config.html') + print(''' +Execute `tox` to test your project. +''') + + +def parse_args(argv): + parser = argparse.ArgumentParser( + description='Command-line script to quickly setup tox.ini for a Python project.' + ) + parser.add_argument( + 'root', type=str, nargs='?', default='.', + help='Custom root directory to write tox.ini to. Defaults to current directory.' + ) + parser.add_argument('--version', action='version', version='%(prog)s ' + __version__) + + args = argv[1:] + return parser.parse_args(args) + + +def main(argv=sys.argv): + args = parse_args(argv) + + d = {} + d['path'] = args.root + + try: + ask_user(d) + except (KeyboardInterrupt, EOFError): + print() + print('[Interrupted.]') + return + + d = process_input(d) + try: + generate(d, overwrite=False) + except Exception: + return 2 + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/robot/lib/python3.8/site-packages/tox/_verlib.py b/robot/lib/python3.8/site-packages/tox/_verlib.py new file mode 100644 index 0000000000000000000000000000000000000000..79f9a11e4fb0f8acea890e5ae609c9765ef1bb3e --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox/_verlib.py @@ -0,0 +1,224 @@ +""" +PEP386-version comparison algorithm. + +(c) Tarek Ziade and others +extracted unmodified from https://bitbucket.org/tarek/distutilsversion +licensed under the PSF license (i guess) + +UPDATE 2017-09-06: removed suggest_normalized_version function as it is not needed by the project +""" +import re + + +class IrrationalVersionError(Exception): + """This is an irrational version.""" + pass + + +class HugeMajorVersionNumError(IrrationalVersionError): + """An irrational version because the major version number is huge + (often because a year or date was used). + + See `error_on_huge_major_num` option in `NormalizedVersion` for details. + This guard can be disabled by setting that option False. + """ + pass + + +# A marker used in the second and third parts of the `parts` tuple, for +# versions that don't have those segments, to sort properly. An example +# of versions in sort order ('highest' last): +# 1.0b1 ((1,0), ('b',1), ('f',)) +# 1.0.dev345 ((1,0), ('f',), ('dev', 345)) +# 1.0 ((1,0), ('f',), ('f',)) +# 1.0.post256.dev345 ((1,0), ('f',), ('f', 'post', 256, 'dev', 345)) +# 1.0.post345 ((1,0), ('f',), ('f', 'post', 345, 'f')) +# ^ ^ ^ +# 'b' < 'f' ---------------------/ | | +# | | +# 'dev' < 'f' < 'post' -------------------/ | +# | +# 'dev' < 'f' ----------------------------------------------/ +# Other letters would do, but 'f' for 'final' is kind of nice. +FINAL_MARKER = ('f',) + +VERSION_RE = re.compile(r''' + ^ + (?P\d+\.\d+) # minimum 'N.N' + (?P(?:\.\d+)*) # any number of extra '.N' segments + (?: + (?P[abc]|rc) # 'a'=alpha, 'b'=beta, 'c'=release candidate + # 'rc'= alias for release candidate + (?P\d+(?:\.\d+)*) + )? + (?P(\.post(?P\d+))?(\.dev(?P\d+))?)? + $''', re.VERBOSE) + + +class NormalizedVersion(object): + """A rational version. + + Good: + 1.2 # equivalent to "1.2.0" + 1.2.0 + 1.2a1 + 1.2.3a2 + 1.2.3b1 + 1.2.3c1 + 1.2.3.4 + TODO: fill this out + + Bad: + 1 # mininum two numbers + 1.2a # release level must have a release serial + 1.2.3b + """ + + def __init__(self, s, error_on_huge_major_num=True): + """Create a NormalizedVersion instance from a version string. + + @param s {str} The version string. + @param error_on_huge_major_num {bool} Whether to consider an + apparent use of a year or full date as the major version number + an error. Default True. One of the observed patterns on PyPI before + the introduction of `NormalizedVersion` was version numbers like this: + 2009.01.03 + 20040603 + 2005.01 + This guard is here to strongly encourage the package author to + use an alternate version, because a release deployed into PyPI + and, e.g. downstream Linux package managers, will forever remove + the possibility of using a version number like "1.0" (i.e. + where the major number is less than that huge major number). + """ + self._parse(s, error_on_huge_major_num) + + @classmethod + def from_parts(cls, version, prerelease=FINAL_MARKER, + devpost=FINAL_MARKER): + return cls(cls.parts_to_str((version, prerelease, devpost))) + + def _parse(self, s, error_on_huge_major_num=True): + """Parses a string version into parts.""" + match = VERSION_RE.search(s) + if not match: + raise IrrationalVersionError(s) + + groups = match.groupdict() + parts = [] + + # main version + block = self._parse_numdots(groups['version'], s, False, 2) + extraversion = groups.get('extraversion') + if extraversion not in ('', None): + block += self._parse_numdots(extraversion[1:], s) + parts.append(tuple(block)) + + # prerelease + prerel = groups.get('prerel') + if prerel is not None: + block = [prerel] + block += self._parse_numdots(groups.get('prerelversion'), s, + pad_zeros_length=1) + parts.append(tuple(block)) + else: + parts.append(FINAL_MARKER) + + # postdev + if groups.get('postdev'): + post = groups.get('post') + dev = groups.get('dev') + postdev = [] + if post is not None: + postdev.extend([FINAL_MARKER[0], 'post', int(post)]) + if dev is None: + postdev.append(FINAL_MARKER[0]) + if dev is not None: + postdev.extend(['dev', int(dev)]) + parts.append(tuple(postdev)) + else: + parts.append(FINAL_MARKER) + self.parts = tuple(parts) + if error_on_huge_major_num and self.parts[0][0] > 1980: + raise HugeMajorVersionNumError( + "huge major version number, %r, " + "which might cause future problems: %r" % (self.parts[0][0], s)) + + def _parse_numdots(self, s, full_ver_str, drop_trailing_zeros=True, + pad_zeros_length=0): + """Parse 'N.N.N' sequences, return a list of ints. + + @param s {str} 'N.N.N..." sequence to be parsed + @param full_ver_str {str} The full version string from which this + comes. Used for error strings. + @param drop_trailing_zeros {bool} Whether to drop trailing zeros + from the returned list. Default True. + @param pad_zeros_length {int} The length to which to pad the + returned list with zeros, if necessary. Default 0. + """ + nums = [] + for n in s.split("."): + if len(n) > 1 and n[0] == '0': + raise IrrationalVersionError( + "cannot have leading zero in " + "version number segment: '%s' in %r" % (n, full_ver_str)) + nums.append(int(n)) + if drop_trailing_zeros: + while nums and nums[-1] == 0: + nums.pop() + while len(nums) < pad_zeros_length: + nums.append(0) + return nums + + def __str__(self): + return self.parts_to_str(self.parts) + + @classmethod + def parts_to_str(cls, parts): + """Transforms a version expressed in tuple into its string + representation.""" + # XXX This doesn't check for invalid tuples + main, prerel, postdev = parts + s = '.'.join(str(v) for v in main) + if prerel is not FINAL_MARKER: + s += prerel[0] + s += '.'.join(str(v) for v in prerel[1:]) + if postdev and postdev is not FINAL_MARKER: + if postdev[0] == 'f': + postdev = postdev[1:] + i = 0 + while i < len(postdev): + if i % 2 == 0: + s += '.' + s += str(postdev[i]) + i += 1 + return s + + def __repr__(self): + return "%s('%s')" % (self.__class__.__name__, self) + + def _cannot_compare(self, other): + raise TypeError("cannot compare %s and %s" + % (type(self).__name__, type(other).__name__)) + + def __eq__(self, other): + if not isinstance(other, NormalizedVersion): + self._cannot_compare(other) + return self.parts == other.parts + + def __lt__(self, other): + if not isinstance(other, NormalizedVersion): + self._cannot_compare(other) + return self.parts < other.parts + + def __ne__(self, other): + return not self.__eq__(other) + + def __gt__(self, other): + return not (self.__lt__(other) or self.__eq__(other)) + + def __le__(self, other): + return self.__eq__(other) or self.__lt__(other) + + def __ge__(self, other): + return self.__eq__(other) or self.__gt__(other) diff --git a/robot/lib/python3.8/site-packages/tox/config.py b/robot/lib/python3.8/site-packages/tox/config.py new file mode 100644 index 0000000000000000000000000000000000000000..ad72bdf307d5c92847de1abd39aea32bf87092cf --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox/config.py @@ -0,0 +1,1358 @@ +from __future__ import print_function + +import argparse +import itertools +import os +import random +import re +import shlex +import string +import sys +from fnmatch import fnmatchcase +from subprocess import list2cmdline + +import pkg_resources +import pluggy +import py + +import tox +import tox.interpreters +from tox import hookspecs +from tox._verlib import NormalizedVersion + +iswin32 = sys.platform == "win32" + +default_factors = {'jython': 'jython', 'pypy': 'pypy', 'pypy3': 'pypy3', + 'py': sys.executable, 'py2': 'python2', 'py3': 'python3'} +for version in '27,34,35,36,37'.split(','): + default_factors['py' + version] = 'python%s.%s' % tuple(version) + +hookimpl = pluggy.HookimplMarker("tox") + +_dummy = object() + +PIP_INSTALL_SHORT_OPTIONS_ARGUMENT = ['-{}'.format(option) for option in [ + 'c', 'e', 'r', 'b', 't', 'd', +]] + +PIP_INSTALL_LONG_OPTIONS_ARGUMENT = ['--{}'.format(option) for option in [ + 'constraint', 'editable', 'requirement', 'build', 'target', 'download', + 'src', 'upgrade-strategy', 'install-options', 'global-option', + 'root', 'prefix', 'no-binary', 'only-binary', 'index-url', + 'extra-index-url', 'find-links', 'proxy', 'retries', 'timeout', + 'exists-action', 'trusted-host', 'client-cert', 'cache-dir', +]] + + +def get_plugin_manager(plugins=()): + # initialize plugin manager + import tox.venv + pm = pluggy.PluginManager("tox") + pm.add_hookspecs(hookspecs) + pm.register(tox.config) + pm.register(tox.interpreters) + pm.register(tox.venv) + pm.register(tox.session) + pm.load_setuptools_entrypoints("tox") + for plugin in plugins: + pm.register(plugin) + pm.check_pending() + return pm + + +class Parser: + """ command line and ini-parser control object. """ + + def __init__(self): + self.argparser = argparse.ArgumentParser( + description="tox options", add_help=False) + self._testenv_attr = [] + + def add_argument(self, *args, **kwargs): + """ add argument to command line parser. This takes the + same arguments that ``argparse.ArgumentParser.add_argument``. + """ + return self.argparser.add_argument(*args, **kwargs) + + def add_testenv_attribute(self, name, type, help, default=None, postprocess=None): + """ add an ini-file variable for "testenv" section. + + Types are specified as strings like "bool", "line-list", "string", "argv", "path", + "argvlist". + + The ``postprocess`` function will be called for each testenv + like ``postprocess(testenv_config=testenv_config, value=value)`` + where ``value`` is the value as read from the ini (or the default value) + and ``testenv_config`` is a :py:class:`tox.config.TestenvConfig` instance + which will receive all ini-variables as object attributes. + + Any postprocess function must return a value which will then be set + as the final value in the testenv section. + """ + self._testenv_attr.append(VenvAttribute(name, type, default, help, postprocess)) + + def add_testenv_attribute_obj(self, obj): + """ add an ini-file variable as an object. + + This works as the ``add_testenv_attribute`` function but expects + "name", "type", "help", and "postprocess" attributes on the object. + """ + assert hasattr(obj, "name") + assert hasattr(obj, "type") + assert hasattr(obj, "help") + assert hasattr(obj, "postprocess") + self._testenv_attr.append(obj) + + def _parse_args(self, args): + return self.argparser.parse_args(args) + + def _format_help(self): + return self.argparser.format_help() + + +class VenvAttribute: + def __init__(self, name, type, default, help, postprocess): + self.name = name + self.type = type + self.default = default + self.help = help + self.postprocess = postprocess + + +class DepOption: + name = "deps" + type = "line-list" + help = "each line specifies a dependency in pip/setuptools format." + default = () + + def postprocess(self, testenv_config, value): + deps = [] + config = testenv_config.config + for depline in value: + m = re.match(r":(\w+):\s*(\S+)", depline) + if m: + iname, name = m.groups() + ixserver = config.indexserver[iname] + else: + name = depline.strip() + ixserver = None + + # we need to process options, in case they contain a space, + # as the subprocess call to pip install will otherwise fail. + + # in case of a short option, we remove the space + for option in PIP_INSTALL_SHORT_OPTIONS_ARGUMENT: + if name.startswith(option): + name = '{}{}'.format( + option, name[len(option):].strip() + ) + + # in case of a long option, we add an equal sign + for option in PIP_INSTALL_LONG_OPTIONS_ARGUMENT: + if name.startswith(option + ' '): + name = '{}={}'.format( + option, name[len(option):].strip() + ) + + name = self._replace_forced_dep(name, config) + deps.append(DepConfig(name, ixserver)) + return deps + + def _replace_forced_dep(self, name, config): + """ + Override the given dependency config name taking --force-dep-version + option into account. + + :param name: dep config, for example ["pkg==1.0", "other==2.0"]. + :param config: Config instance + :return: the new dependency that should be used for virtual environments + """ + if not config.option.force_dep: + return name + for forced_dep in config.option.force_dep: + if self._is_same_dep(forced_dep, name): + return forced_dep + return name + + @classmethod + def _is_same_dep(cls, dep1, dep2): + """ + Returns True if both dependency definitions refer to the + same package, even if versions differ. + """ + dep1_name = pkg_resources.Requirement.parse(dep1).project_name + try: + dep2_name = pkg_resources.Requirement.parse(dep2).project_name + except pkg_resources.RequirementParseError: + # we couldn't parse a version, probably a URL + return False + return dep1_name == dep2_name + + +class PosargsOption: + name = "args_are_paths" + type = "bool" + default = True + help = "treat positional args in commands as paths" + + def postprocess(self, testenv_config, value): + config = testenv_config.config + args = config.option.args + if args: + if value: + args = [] + for arg in config.option.args: + if arg: + origpath = config.invocationcwd.join(arg, abs=True) + if origpath.check(): + arg = testenv_config.changedir.bestrelpath(origpath) + args.append(arg) + testenv_config._reader.addsubstitutions(args) + return value + + +class InstallcmdOption: + name = "install_command" + type = "argv" + default = "pip install {opts} {packages}" + help = "install command for dependencies and package under test." + + def postprocess(self, testenv_config, value): + if '{packages}' not in value: + raise tox.exception.ConfigError( + "'install_command' must contain '{packages}' substitution") + return value + + +def parseconfig(args, plugins=()): + """ + :param list[str] args: list of arguments. + :type pkg: str + :rtype: :class:`Config` + :raise SystemExit: toxinit file is not found + """ + + pm = get_plugin_manager(plugins) + # prepare command line options + parser = Parser() + pm.hook.tox_addoption(parser=parser) + + # parse command line options + option = parser._parse_args(args) + interpreters = tox.interpreters.Interpreters(hook=pm.hook) + config = Config(pluginmanager=pm, option=option, interpreters=interpreters) + config._parser = parser + config._testenv_attr = parser._testenv_attr + if config.option.version: + print(get_version_info(pm)) + raise SystemExit(0) + # parse ini file + basename = config.option.configfile + if os.path.isfile(basename): + inipath = py.path.local(basename) + elif os.path.isdir(basename): + # Assume 'tox.ini' filename if directory was passed + inipath = py.path.local(os.path.join(basename, 'tox.ini')) + else: + for path in py.path.local().parts(reverse=True): + inipath = path.join(basename) + if inipath.check(): + break + else: + inipath = py.path.local().join('setup.cfg') + if not inipath.check(): + helpoptions = option.help or option.helpini + feedback("toxini file %r not found" % (basename), + sysexit=not helpoptions) + if helpoptions: + return config + + try: + parseini(config, inipath) + except tox.exception.InterpreterNotFound: + exn = sys.exc_info()[1] + # Use stdout to match test expectations + print("ERROR: " + str(exn)) + + # post process config object + pm.hook.tox_configure(config=config) + + return config + + +def feedback(msg, sysexit=False): + print("ERROR: " + msg, file=sys.stderr) + if sysexit: + raise SystemExit(1) + + +def get_version_info(pm): + out = ["%s imported from %s" % (tox.__version__, tox.__file__)] + plugin_dist_info = pm.list_plugin_distinfo() + if plugin_dist_info: + out.append('registered plugins:') + for mod, egg_info in plugin_dist_info: + source = getattr(mod, '__file__', repr(mod)) + out.append(" %s-%s at %s" % ( + egg_info.project_name, egg_info.version, source)) + return '\n'.join(out) + + +class SetenvDict(object): + def __init__(self, definitions, reader): + self.definitions = definitions + self.reader = reader + self.resolved = {} + self._lookupstack = [] + + def __repr__(self): + return "%s: %s" % (self.__class__.__name__, self.definitions) + + def __contains__(self, name): + return name in self.definitions + + def get(self, name, default=None): + try: + return self.resolved[name] + except KeyError: + try: + if name in self._lookupstack: + raise KeyError(name) + val = self.definitions[name] + except KeyError: + return os.environ.get(name, default) + self._lookupstack.append(name) + try: + self.resolved[name] = res = self.reader._replace(val) + finally: + self._lookupstack.pop() + return res + + def __getitem__(self, name): + x = self.get(name, _dummy) + if x is _dummy: + raise KeyError(name) + return x + + def keys(self): + return self.definitions.keys() + + def __setitem__(self, name, value): + self.definitions[name] = value + self.resolved[name] = value + + +@hookimpl +def tox_addoption(parser): + # formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("--version", action="store_true", dest="version", + help="report version information to stdout.") + parser.add_argument("-h", "--help", action="store_true", dest="help", + help="show help about options") + parser.add_argument("--help-ini", "--hi", action="store_true", dest="helpini", + help="show help about ini-names") + parser.add_argument("-v", action='count', dest="verbose_level", default=0, + help="increase verbosity of reporting output. -vv mode turns off " + "output redirection for package installation") + parser.add_argument("-q", action="count", dest="quiet_level", default=0, + help="progressively silence reporting output.") + parser.add_argument("--showconfig", action="store_true", + help="show configuration information for all environments. ") + parser.add_argument("-l", "--listenvs", action="store_true", + dest="listenvs", help="show list of test environments " + "(with description if verbose)") + parser.add_argument("-a", "--listenvs-all", action="store_true", + dest="listenvs_all", + help="show list of all defined environments" + "(with description if verbose)") + parser.add_argument("-c", action="store", default="tox.ini", + dest="configfile", + help="config file name or directory with 'tox.ini' file.") + parser.add_argument("-e", action="append", dest="env", + metavar="envlist", + help="work against specified environments (ALL selects all).") + parser.add_argument("--notest", action="store_true", dest="notest", + help="skip invoking test commands.") + parser.add_argument("--sdistonly", action="store_true", dest="sdistonly", + help="only perform the sdist packaging activity.") + parser.add_argument("--installpkg", action="store", default=None, + metavar="PATH", + help="use specified package for installation into venv, instead of " + "creating an sdist.") + parser.add_argument("--develop", action="store_true", dest="develop", + help="install package in the venv using 'setup.py develop' via " + "'pip -e .'") + parser.add_argument('-i', '--index-url', action="append", + dest="indexurl", metavar="URL", + help="set indexserver url (if URL is of form name=url set the " + "url for the 'name' indexserver, specifically)") + parser.add_argument("--pre", action="store_true", dest="pre", + help="install pre-releases and development versions of dependencies. " + "This will pass the --pre option to install_command " + "(pip by default).") + parser.add_argument("-r", "--recreate", action="store_true", + dest="recreate", + help="force recreation of virtual environments") + parser.add_argument("--result-json", action="store", + dest="resultjson", metavar="PATH", + help="write a json file with detailed information " + "about all commands and results involved.") + + # We choose 1 to 4294967295 because it is the range of PYTHONHASHSEED. + parser.add_argument("--hashseed", action="store", + metavar="SEED", default=None, + help="set PYTHONHASHSEED to SEED before running commands. " + "Defaults to a random integer in the range [1, 4294967295] " + "([1, 1024] on Windows). " + "Passing 'noset' suppresses this behavior.") + parser.add_argument("--force-dep", action="append", + metavar="REQ", default=None, + help="Forces a certain version of one of the dependencies " + "when configuring the virtual environment. REQ Examples " + "'pytest<2.7' or 'django>=1.6'.") + parser.add_argument("--sitepackages", action="store_true", + help="override sitepackages setting to True in all envs") + parser.add_argument("--alwayscopy", action="store_true", + help="override alwayscopy setting to True in all envs") + parser.add_argument("--skip-missing-interpreters", action="store_true", + help="don't fail tests for missing interpreters") + parser.add_argument("--workdir", action="store", + dest="workdir", metavar="PATH", default=None, + help="tox working directory") + + parser.add_argument("args", nargs="*", + help="additional arguments available to command positional substitution") + + parser.add_testenv_attribute( + name="envdir", type="path", default="{toxworkdir}/{envname}", + help="set venv directory -- be very careful when changing this as tox " + "will remove this directory when recreating an environment") + + # add various core venv interpreter attributes + def setenv(testenv_config, value): + setenv = value + config = testenv_config.config + if "PYTHONHASHSEED" not in setenv and config.hashseed is not None: + setenv['PYTHONHASHSEED'] = config.hashseed + return setenv + + parser.add_testenv_attribute( + name="setenv", type="dict_setenv", postprocess=setenv, + help="list of X=Y lines with environment variable settings") + + def basepython_default(testenv_config, value): + if value is None: + for f in testenv_config.factors: + if f in default_factors: + return default_factors[f] + return sys.executable + return str(value) + + parser.add_testenv_attribute( + name="basepython", type="string", default=None, postprocess=basepython_default, + help="executable name or path of interpreter used to create a " + "virtual test environment.") + + def merge_description(testenv_config, value): + """the reader by default joins generated description with new line, + replace new line with space""" + return value.replace('\n', ' ') + + parser.add_testenv_attribute( + name="description", type="string", default='', postprocess=merge_description, + help="short description of this environment") + + parser.add_testenv_attribute( + name="envtmpdir", type="path", default="{envdir}/tmp", + help="venv temporary directory") + + parser.add_testenv_attribute( + name="envlogdir", type="path", default="{envdir}/log", + help="venv log directory") + + parser.add_testenv_attribute( + name="downloadcache", type="string", default=None, + help="(ignored) has no effect anymore, pip-8 uses local caching by default") + + parser.add_testenv_attribute( + name="changedir", type="path", default="{toxinidir}", + help="directory to change to when running commands") + + parser.add_testenv_attribute_obj(PosargsOption()) + + parser.add_testenv_attribute( + name="skip_install", type="bool", default=False, + help="Do not install the current package. This can be used when " + "you need the virtualenv management but do not want to install " + "the current package") + + parser.add_testenv_attribute( + name="ignore_errors", type="bool", default=False, + help="if set to True all commands will be executed irrespective of their " + "result error status.") + + def recreate(testenv_config, value): + if testenv_config.config.option.recreate: + return True + return value + + parser.add_testenv_attribute( + name="recreate", type="bool", default=False, postprocess=recreate, + help="always recreate this test environment.") + + def passenv(testenv_config, value): + # Flatten the list to deal with space-separated values. + value = list( + itertools.chain.from_iterable( + [x.split(' ') for x in value])) + + passenv = { + "PATH", "PIP_INDEX_URL", "LANG", "LANGUAGE", "LD_LIBRARY_PATH" + } + + # read in global passenv settings + p = os.environ.get("TOX_TESTENV_PASSENV", None) + if p is not None: + env_values = [x for x in p.split() if x] + value.extend(env_values) + + # we ensure that tmp directory settings are passed on + # we could also set it to the per-venv "envtmpdir" + # but this leads to very long paths when run with jenkins + # so we just pass it on by default for now. + if sys.platform == "win32": + passenv.add("SYSTEMDRIVE") # needed for pip6 + passenv.add("SYSTEMROOT") # needed for python's crypto module + passenv.add("PATHEXT") # needed for discovering executables + passenv.add("COMSPEC") # needed for distutils cygwincompiler + passenv.add("TEMP") + passenv.add("TMP") + # for `multiprocessing.cpu_count()` on Windows + # (prior to Python 3.4). + passenv.add("NUMBER_OF_PROCESSORS") + passenv.add("PROCESSOR_ARCHITECTURE") # platform.machine() + passenv.add("USERPROFILE") # needed for `os.path.expanduser()` + passenv.add("MSYSTEM") # fixes #429 + else: + passenv.add("TMPDIR") + for spec in value: + for name in os.environ: + if fnmatchcase(name.upper(), spec.upper()): + passenv.add(name) + return passenv + + parser.add_testenv_attribute( + name="passenv", type="line-list", postprocess=passenv, + help="environment variables needed during executing test commands " + "(taken from invocation environment). Note that tox always " + "passes through some basic environment variables which are " + "needed for basic functioning of the Python system. " + "See --showconfig for the eventual passenv setting.") + + parser.add_testenv_attribute( + name="whitelist_externals", type="line-list", + help="each lines specifies a path or basename for which tox will not warn " + "about it coming from outside the test environment.") + + parser.add_testenv_attribute( + name="platform", type="string", default=".*", + help="regular expression which must match against ``sys.platform``. " + "otherwise testenv will be skipped.") + + def sitepackages(testenv_config, value): + return testenv_config.config.option.sitepackages or value + + def alwayscopy(testenv_config, value): + return testenv_config.config.option.alwayscopy or value + + parser.add_testenv_attribute( + name="sitepackages", type="bool", default=False, postprocess=sitepackages, + help="Set to ``True`` if you want to create virtual environments that also " + "have access to globally installed packages.") + + parser.add_testenv_attribute( + name="alwayscopy", type="bool", default=False, postprocess=alwayscopy, + help="Set to ``True`` if you want virtualenv to always copy files rather " + "than symlinking.") + + def pip_pre(testenv_config, value): + return testenv_config.config.option.pre or value + + parser.add_testenv_attribute( + name="pip_pre", type="bool", default=False, postprocess=pip_pre, + help="If ``True``, adds ``--pre`` to the ``opts`` passed to " + "the install command. ") + + def develop(testenv_config, value): + option = testenv_config.config.option + return not option.installpkg and (value or option.develop) + + parser.add_testenv_attribute( + name="usedevelop", type="bool", postprocess=develop, default=False, + help="install package in develop/editable mode") + + parser.add_testenv_attribute_obj(InstallcmdOption()) + + parser.add_testenv_attribute( + name="list_dependencies_command", + type="argv", + default="pip freeze", + help="list dependencies for a virtual environment") + + parser.add_testenv_attribute_obj(DepOption()) + + parser.add_testenv_attribute( + name="commands", type="argvlist", default="", + help="each line specifies a test command and can use substitution.") + + parser.add_testenv_attribute( + "ignore_outcome", type="bool", default=False, + help="if set to True a failing result of this testenv will not make " + "tox fail, only a warning will be produced") + + parser.add_testenv_attribute( + "extras", type="line-list", + help="list of extras to install with the source distribution or " + "develop install") + + +class Config(object): + """ Global Tox config object. """ + + def __init__(self, pluginmanager, option, interpreters): + #: dictionary containing envname to envconfig mappings + self.envconfigs = {} + self.invocationcwd = py.path.local() + self.interpreters = interpreters + self.pluginmanager = pluginmanager + #: option namespace containing all parsed command line options + self.option = option + + @property + def homedir(self): + homedir = get_homedir() + if homedir is None: + homedir = self.toxinidir # XXX good idea? + return homedir + + +class TestenvConfig: + """ Testenv Configuration object. + In addition to some core attributes/properties this config object holds all + per-testenv ini attributes as attributes, see "tox --help-ini" for an overview. + """ + + def __init__(self, envname, config, factors, reader): + #: test environment name + self.envname = envname + #: global tox config object + self.config = config + #: set of factors + self.factors = factors + self._reader = reader + self.missing_subs = [] + """Holds substitutions that could not be resolved. + + Pre 2.8.1 missing substitutions crashed with a ConfigError although this would not be a + problem if the env is not part of the current testrun. So we need to remember this and + check later when the testenv is actually run and crash only then. + """ + + def get_envbindir(self): + """ path to directory where scripts/binaries reside. """ + if sys.platform == "win32" and "jython" not in self.basepython and \ + "pypy" not in self.basepython: + return self.envdir.join("Scripts") + else: + return self.envdir.join("bin") + + @property + def envbindir(self): + return self.get_envbindir() + + @property + def envpython(self): + """ path to python executable. """ + return self.get_envpython() + + def get_envpython(self): + """ path to python/jython executable. """ + if "jython" in str(self.basepython): + name = "jython" + else: + name = "python" + return self.envbindir.join(name) + + def get_envsitepackagesdir(self): + """ return sitepackagesdir of the virtualenv environment. + (only available during execution, not parsing) + """ + x = self.config.interpreters.get_sitepackagesdir( + info=self.python_info, + envdir=self.envdir) + return x + + @property + def python_info(self): + """ return sitepackagesdir of the virtualenv environment. """ + return self.config.interpreters.get_info(envconfig=self) + + def getsupportedinterpreter(self): + if sys.platform == "win32" and self.basepython and \ + "jython" in self.basepython: + raise tox.exception.UnsupportedInterpreter( + "Jython/Windows does not support installing scripts") + info = self.config.interpreters.get_info(envconfig=self) + if not info.executable: + raise tox.exception.InterpreterNotFound(self.basepython) + if not info.version_info: + raise tox.exception.InvocationError( + 'Failed to get version_info for %s: %s' % (info.name, info.err)) + return info.executable + + +testenvprefix = "testenv:" + + +def get_homedir(): + try: + return py.path.local._gethomedir() + except Exception: + return None + + +def make_hashseed(): + max_seed = 4294967295 + if sys.platform == 'win32': + max_seed = 1024 + return str(random.randint(1, max_seed)) + + +class parseini: + def __init__(self, config, inipath): + config.toxinipath = inipath + config.toxinidir = config.toxinipath.dirpath() + + self._cfg = py.iniconfig.IniConfig(config.toxinipath) + config._cfg = self._cfg + self.config = config + + if inipath.basename == 'setup.cfg': + prefix = 'tox' + else: + prefix = None + ctxname = getcontextname() + if ctxname == "jenkins": + reader = SectionReader("tox:jenkins", self._cfg, prefix=prefix, + fallbacksections=['tox']) + distshare_default = "{toxworkdir}/distshare" + elif not ctxname: + reader = SectionReader("tox", self._cfg, prefix=prefix) + distshare_default = "{homedir}/.tox/distshare" + else: + raise ValueError("invalid context") + + if config.option.hashseed is None: + hashseed = make_hashseed() + elif config.option.hashseed == 'noset': + hashseed = None + else: + hashseed = config.option.hashseed + config.hashseed = hashseed + + reader.addsubstitutions(toxinidir=config.toxinidir, + homedir=config.homedir) + # As older versions of tox may have bugs or incompatibilities that + # prevent parsing of tox.ini this must be the first thing checked. + config.minversion = reader.getstring("minversion", None) + if config.minversion: + minversion = NormalizedVersion(self.config.minversion) + toxversion = NormalizedVersion(tox.__version__) + if toxversion < minversion: + raise tox.exception.MinVersionError( + "tox version is %s, required is at least %s" % ( + toxversion, minversion)) + if config.option.workdir is None: + config.toxworkdir = reader.getpath("toxworkdir", "{toxinidir}/.tox") + else: + config.toxworkdir = config.toxinidir.join(config.option.workdir, abs=True) + + if not config.option.skip_missing_interpreters: + config.option.skip_missing_interpreters = \ + reader.getbool("skip_missing_interpreters", False) + + # determine indexserver dictionary + config.indexserver = {'default': IndexServerConfig('default')} + prefix = "indexserver" + for line in reader.getlist(prefix): + name, url = map(lambda x: x.strip(), line.split("=", 1)) + config.indexserver[name] = IndexServerConfig(name, url) + + override = False + if config.option.indexurl: + for urldef in config.option.indexurl: + m = re.match(r"\W*(\w+)=(\S+)", urldef) + if m is None: + url = urldef + name = "default" + else: + name, url = m.groups() + if not url: + url = None + if name != "ALL": + config.indexserver[name].url = url + else: + override = url + # let ALL override all existing entries + if override: + for name in config.indexserver: + config.indexserver[name] = IndexServerConfig(name, override) + + reader.addsubstitutions(toxworkdir=config.toxworkdir) + config.distdir = reader.getpath("distdir", "{toxworkdir}/dist") + reader.addsubstitutions(distdir=config.distdir) + config.distshare = reader.getpath("distshare", distshare_default) + reader.addsubstitutions(distshare=config.distshare) + config.sdistsrc = reader.getpath("sdistsrc", None) + config.setupdir = reader.getpath("setupdir", "{toxinidir}") + config.logdir = config.toxworkdir.join("log") + + config.envlist, all_envs = self._getenvdata(reader) + + # factors used in config or predefined + known_factors = self._list_section_factors("testenv") + known_factors.update(default_factors) + known_factors.add("python") + + # factors stated in config envlist + stated_envlist = reader.getstring("envlist", replace=False) + if stated_envlist: + for env in _split_env(stated_envlist): + known_factors.update(env.split('-')) + + # configure testenvs + for name in all_envs: + section = testenvprefix + name + factors = set(name.split('-')) + if section in self._cfg or factors <= known_factors: + config.envconfigs[name] = self.make_envconfig(name, section, reader._subs, config) + + all_develop = all(name in config.envconfigs and + config.envconfigs[name].usedevelop for name in config.envlist) + + config.skipsdist = reader.getbool("skipsdist", all_develop) + + def _list_section_factors(self, section): + factors = set() + if section in self._cfg: + for _, value in self._cfg[section].items(): + exprs = re.findall(r'^([\w{}\.!,-]+)\:\s+', value, re.M) + factors.update(*mapcat(_split_factor_expr_all, exprs)) + return factors + + def make_envconfig(self, name, section, subs, config, replace=True): + factors = set(name.split('-')) + reader = SectionReader(section, self._cfg, fallbacksections=["testenv"], + factors=factors) + tc = TestenvConfig(name, config, factors, reader) + reader.addsubstitutions( + envname=name, envbindir=tc.get_envbindir, envsitepackagesdir=tc.get_envsitepackagesdir, + envpython=tc.get_envpython, **subs) + for env_attr in config._testenv_attr: + atype = env_attr.type + try: + if atype in ("bool", "path", "string", "dict", "dict_setenv", "argv", "argvlist"): + meth = getattr(reader, "get" + atype) + res = meth(env_attr.name, env_attr.default, replace=replace) + elif atype == "space-separated-list": + res = reader.getlist(env_attr.name, sep=" ") + elif atype == "line-list": + res = reader.getlist(env_attr.name, sep="\n") + else: + raise ValueError("unknown type %r" % (atype,)) + if env_attr.postprocess: + res = env_attr.postprocess(testenv_config=tc, value=res) + except tox.exception.MissingSubstitution as e: + tc.missing_subs.append(e.name) + res = e.FLAG + setattr(tc, env_attr.name, res) + if atype in ("path", "string"): + reader.addsubstitutions(**{env_attr.name: res}) + return tc + + def _getenvdata(self, reader): + envstr = self.config.option.env \ + or os.environ.get("TOXENV") \ + or reader.getstring("envlist", replace=False) \ + or [] + envlist = _split_env(envstr) + + # collect section envs + all_envs = set(envlist) - {"ALL"} + for section in self._cfg: + if section.name.startswith(testenvprefix): + all_envs.add(section.name[len(testenvprefix):]) + if not all_envs: + all_envs.add("python") + + if not envlist or "ALL" in envlist: + envlist = sorted(all_envs) + + return envlist, all_envs + + +def _split_env(env): + """if handed a list, action="append" was used for -e """ + if not isinstance(env, list): + env = [e.split('#', 1)[0].strip() for e in env.split('\n')] + env = ','.join([e for e in env if e]) + env = [env] + return mapcat(_expand_envstr, env) + + +def _is_negated_factor(factor): + return factor.startswith('!') + + +def _base_factor_name(factor): + return factor[1:] if _is_negated_factor(factor) else factor + + +def _split_factor_expr(expr): + def split_single(e): + raw = e.split('-') + included = {_base_factor_name(factor) for factor in raw + if not _is_negated_factor(factor)} + excluded = {_base_factor_name(factor) for factor in raw + if _is_negated_factor(factor)} + return included, excluded + + partial_envs = _expand_envstr(expr) + return [split_single(e) for e in partial_envs] + + +def _split_factor_expr_all(expr): + partial_envs = _expand_envstr(expr) + return [{_base_factor_name(factor) for factor in e.split('-')} + for e in partial_envs] + + +def _expand_envstr(envstr): + # split by commas not in groups + tokens = re.split(r'((?:\{[^}]+\})+)|,', envstr) + envlist = [''.join(g).strip() + for k, g in itertools.groupby(tokens, key=bool) if k] + + def expand(env): + tokens = re.split(r'\{([^}]+)\}', env) + parts = [re.sub('\s+', '', token).split(',') for token in tokens] + return [''.join(variant) for variant in itertools.product(*parts)] + + return mapcat(expand, envlist) + + +def mapcat(f, seq): + return list(itertools.chain.from_iterable(map(f, seq))) + + +class DepConfig: + def __init__(self, name, indexserver=None): + self.name = name + self.indexserver = indexserver + + def __str__(self): + if self.indexserver: + if self.indexserver.name == "default": + return self.name + return ":%s:%s" % (self.indexserver.name, self.name) + return str(self.name) + + __repr__ = __str__ + + +class IndexServerConfig: + def __init__(self, name, url=None): + self.name = name + self.url = url + + +#: Check value matches substitution form +#: of referencing value from other section. E.g. {[base]commands} +is_section_substitution = re.compile(r"{\[[^{}\s]+\]\S+?}").match + + +class SectionReader: + def __init__(self, section_name, cfgparser, fallbacksections=None, + factors=(), prefix=None): + if prefix is None: + self.section_name = section_name + else: + self.section_name = "%s:%s" % (prefix, section_name) + self._cfg = cfgparser + self.fallbacksections = fallbacksections or [] + self.factors = factors + self._subs = {} + self._subststack = [] + self._setenv = None + + def get_environ_value(self, name): + if self._setenv is None: + return os.environ.get(name) + return self._setenv.get(name) + + def addsubstitutions(self, _posargs=None, **kw): + self._subs.update(kw) + if _posargs: + self.posargs = _posargs + + def getpath(self, name, defaultpath, replace=True): + path = self.getstring(name, defaultpath, replace=replace) + if path is not None: + toxinidir = self._subs['toxinidir'] + return toxinidir.join(path, abs=True) + + def getlist(self, name, sep="\n"): + s = self.getstring(name, None) + if s is None: + return [] + return [x.strip() for x in s.split(sep) if x.strip()] + + def getdict(self, name, default=None, sep="\n", replace=True): + value = self.getstring(name, None, replace=replace) + return self._getdict(value, default=default, sep=sep, replace=replace) + + def getdict_setenv(self, name, default=None, sep="\n", replace=True): + value = self.getstring(name, None, replace=replace, crossonly=True) + definitions = self._getdict(value, default=default, sep=sep, replace=replace) + self._setenv = SetenvDict(definitions, reader=self) + return self._setenv + + def _getdict(self, value, default, sep, replace=True): + if value is None or not replace: + return default or {} + + d = {} + for line in value.split(sep): + if line.strip(): + name, rest = line.split('=', 1) + d[name.strip()] = rest.strip() + + return d + + def getbool(self, name, default=None, replace=True): + s = self.getstring(name, default, replace=replace) + if not s or not replace: + s = default + if s is None: + raise KeyError("no config value [%s] %s found" % ( + self.section_name, name)) + + if not isinstance(s, bool): + if s.lower() == "true": + s = True + elif s.lower() == "false": + s = False + else: + raise tox.exception.ConfigError( + "boolean value %r needs to be 'True' or 'False'") + return s + + def getargvlist(self, name, default="", replace=True): + s = self.getstring(name, default, replace=False) + return _ArgvlistReader.getargvlist(self, s, replace=replace) + + def getargv(self, name, default="", replace=True): + return self.getargvlist(name, default, replace=replace)[0] + + def getstring(self, name, default=None, replace=True, crossonly=False): + x = None + for s in [self.section_name] + self.fallbacksections: + try: + x = self._cfg[s][name] + break + except KeyError: + continue + + if x is None: + x = default + else: + x = self._apply_factors(x) + + if replace and x and hasattr(x, 'replace'): + x = self._replace(x, name=name, crossonly=crossonly) + # print "getstring", self.section_name, name, "returned", repr(x) + return x + + def _apply_factors(self, s): + def factor_line(line): + m = re.search(r'^([\w{}\.!,-]+)\:\s+(.+)', line) + if not m: + return line + + expr, line = m.groups() + if any(included <= self.factors and not + any(x in self.factors for x in excluded) + for included, excluded in _split_factor_expr(expr)): + return line + + lines = s.strip().splitlines() + return '\n'.join(filter(None, map(factor_line, lines))) + + def _replace(self, value, name=None, section_name=None, crossonly=False): + if '{' not in value: + return value + + section_name = section_name if section_name else self.section_name + self._subststack.append((section_name, name)) + try: + replaced = Replacer(self, crossonly=crossonly).do_replace(value) + assert self._subststack.pop() == (section_name, name) + except tox.exception.MissingSubstitution: + if not section_name.startswith(testenvprefix): + raise tox.exception.ConfigError( + "substitution env:%r: unknown or recursive definition in " + "section %r." % (value, section_name)) + raise + return replaced + + +class Replacer: + RE_ITEM_REF = re.compile( + r''' + (?[^[:{}]+):)? # optional sub_type for special rules + (?P(?:\[[^,{}]*\])?[^:,{}]*) # substitution key + (?::(?P[^{}]*))? # default value + [}] + ''', re.VERBOSE) + + def __init__(self, reader, crossonly=False): + self.reader = reader + self.crossonly = crossonly + + def do_replace(self, value): + ''' + Recursively expand substitutions starting from the innermost expression + ''' + + def substitute_once(x): + return self.RE_ITEM_REF.sub(self._replace_match, x) + + expanded = substitute_once(value) + + while expanded != value: # substitution found + value = expanded + expanded = substitute_once(value) + + return expanded + + def _replace_match(self, match): + g = match.groupdict() + sub_value = g['substitution_value'] + if self.crossonly: + if sub_value.startswith("["): + return self._substitute_from_other_section(sub_value) + # in crossonly we return all other hits verbatim + start, end = match.span() + return match.string[start:end] + + # special case: all empty values means ":" which is os.pathsep + if not any(g.values()): + return os.pathsep + + # special case: opts and packages. Leave {opts} and + # {packages} intact, they are replaced manually in + # _venv.VirtualEnv.run_install_command. + if sub_value in ('opts', 'packages'): + return '{%s}' % sub_value + + try: + sub_type = g['sub_type'] + except KeyError: + raise tox.exception.ConfigError( + "Malformed substitution; no substitution type provided") + + if sub_type == "env": + return self._replace_env(match) + if sub_type is not None: + raise tox.exception.ConfigError( + "No support for the %s substitution type" % sub_type) + return self._replace_substitution(match) + + def _replace_env(self, match): + envkey = match.group('substitution_value') + if not envkey: + raise tox.exception.ConfigError('env: requires an environment variable name') + default = match.group('default_value') + envvalue = self.reader.get_environ_value(envkey) + if envvalue is not None: + return envvalue + if default is not None: + return default + raise tox.exception.MissingSubstitution(envkey) + + def _substitute_from_other_section(self, key): + if key.startswith("[") and "]" in key: + i = key.find("]") + section, item = key[1:i], key[i + 1:] + cfg = self.reader._cfg + if section in cfg and item in cfg[section]: + if (section, item) in self.reader._subststack: + raise ValueError('%s already in %s' % ( + (section, item), self.reader._subststack)) + x = str(cfg[section][item]) + return self.reader._replace(x, name=item, section_name=section, + crossonly=self.crossonly) + + raise tox.exception.ConfigError( + "substitution key %r not found" % key) + + def _replace_substitution(self, match): + sub_key = match.group('substitution_value') + val = self.reader._subs.get(sub_key, None) + if val is None: + val = self._substitute_from_other_section(sub_key) + if callable(val): + val = val() + return str(val) + + +class _ArgvlistReader: + @classmethod + def getargvlist(cls, reader, value, replace=True): + """Parse ``commands`` argvlist multiline string. + + :param SectionReader reader: reader to be used. + :param str value: Content stored by key. + + :rtype: list[list[str]] + :raise :class:`tox.exception.ConfigError`: + line-continuation ends nowhere while resolving for specified section + """ + commands = [] + current_command = "" + for line in value.splitlines(): + line = line.rstrip() + if not line: + continue + if line.endswith("\\"): + current_command += " " + line[:-1] + continue + current_command += line + + if is_section_substitution(current_command): + replaced = reader._replace(current_command, crossonly=True) + commands.extend(cls.getargvlist(reader, replaced)) + else: + commands.append(cls.processcommand(reader, current_command, replace)) + current_command = "" + else: + if current_command: + raise tox.exception.ConfigError( + "line-continuation ends nowhere while resolving for [%s] %s" % + (reader.section_name, "commands")) + return commands + + @classmethod + def processcommand(cls, reader, command, replace=True): + posargs = getattr(reader, "posargs", "") + posargs_string = list2cmdline([x for x in posargs if x]) + + # Iterate through each word of the command substituting as + # appropriate to construct the new command string. This + # string is then broken up into exec argv components using + # shlex. + if replace: + newcommand = "" + for word in CommandParser(command).words(): + if word == "{posargs}" or word == "[]": + newcommand += posargs_string + continue + elif word.startswith("{posargs:") and word.endswith("}"): + if posargs: + newcommand += posargs_string + continue + else: + word = word[9:-1] + new_arg = "" + new_word = reader._replace(word) + new_word = reader._replace(new_word) + new_word = new_word.replace('\\{', '{').replace('\\}', '}') + new_arg += new_word + newcommand += new_arg + else: + newcommand = command + + # Construct shlex object that will not escape any values, + # use all values as is in argv. + shlexer = shlex.shlex(newcommand, posix=True) + shlexer.whitespace_split = True + shlexer.escape = '' + return list(shlexer) + + +class CommandParser(object): + class State(object): + def __init__(self): + self.word = '' + self.depth = 0 + self.yield_words = [] + + def __init__(self, command): + self.command = command + + def words(self): + ps = CommandParser.State() + + def word_has_ended(): + return ((cur_char in string.whitespace and ps.word and + ps.word[-1] not in string.whitespace) or + (cur_char == '{' and ps.depth == 0 and not ps.word.endswith('\\')) or + (ps.depth == 0 and ps.word and ps.word[-1] == '}') or + (cur_char not in string.whitespace and ps.word and + ps.word.strip() == '')) + + def yield_this_word(): + yieldword = ps.word + ps.word = '' + if yieldword: + ps.yield_words.append(yieldword) + + def yield_if_word_ended(): + if word_has_ended(): + yield_this_word() + + def accumulate(): + ps.word += cur_char + + def push_substitution(): + ps.depth += 1 + + def pop_substitution(): + ps.depth -= 1 + + for cur_char in self.command: + if cur_char in string.whitespace: + if ps.depth == 0: + yield_if_word_ended() + accumulate() + elif cur_char == '{': + yield_if_word_ended() + accumulate() + push_substitution() + elif cur_char == '}': + accumulate() + pop_substitution() + else: + yield_if_word_ended() + accumulate() + + if ps.word.strip(): + yield_this_word() + return ps.yield_words + + +def getcontextname(): + if any(env in os.environ for env in ['JENKINS_URL', 'HUDSON_URL']): + return 'jenkins' + return None diff --git a/robot/lib/python3.8/site-packages/tox/hookspecs.py b/robot/lib/python3.8/site-packages/tox/hookspecs.py new file mode 100644 index 0000000000000000000000000000000000000000..ec177b7a8a5a0353c6ad7ffb1ade352a98fceba4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox/hookspecs.py @@ -0,0 +1,114 @@ +""" Hook specifications for tox. + +""" +from pluggy import HookimplMarker +from pluggy import HookspecMarker + +hookspec = HookspecMarker("tox") +hookimpl = HookimplMarker("tox") + + +@hookspec +def tox_addoption(parser): + """ add command line options to the argparse-style parser object.""" + + +@hookspec +def tox_configure(config): + """ called after command line options have been parsed and the ini-file has + been read. Please be aware that the config object layout may change as its + API was not designed yet wrt to providing stability (it was an internal + thing purely before tox-2.0). """ + + +@hookspec(firstresult=True) +def tox_get_python_executable(envconfig): + """ return a python executable for the given python base name. + The first plugin/hook which returns an executable path will determine it. + + ``envconfig`` is the testenv configuration which contains + per-testenv configuration, notably the ``.envname`` and ``.basepython`` + setting. + """ + + +@hookspec(firstresult=True) +def tox_testenv_create(venv, action): + """ [experimental] perform creation action for this venv. + + Some example usage: + + - To *add* behavior but still use tox's implementation to set up a + virtualenv, implement this hook but do not return a value (or explicitly + return ``None``). + - To *override* tox's virtualenv creation, implement this hook and return + a non-``None`` value. + + .. note:: This api is experimental due to the unstable api of + :class:`tox.venv.VirtualEnv`. + + .. note:: This hook uses ``firstresult=True`` (see pluggy_) -- hooks + implementing this will be run until one returns non-``None``. + + .. _pluggy: http://pluggy.readthedocs.io/en/latest/#first-result-only + """ + + +@hookspec(firstresult=True) +def tox_testenv_install_deps(venv, action): + """ [experimental] perform install dependencies action for this venv. + + Some example usage: + + - To *add* behavior but still use tox's implementation to install + dependencies, implement this hook but do not return a value (or + explicitly return ``None``). One use-case may be to install (or ensure) + non-python dependencies such as debian packages. + - To *override* tox's installation of dependencies, implement this hook + and return a non-``None`` value. One use-case may be to install via + a different installation tool such as `pip-accel`_ or `pip-faster`_. + + .. note:: This api is experimental due to the unstable api of + :class:`tox.venv.VirtualEnv`. + + .. note:: This hook uses ``firstresult=True`` (see pluggy_) -- hooks + implementing this will be run until one returns non-``None``. + + .. _pip-accel: https://github.com/paylogic/pip-accel + .. _pip-faster: https://github.com/Yelp/venv-update + .. _pluggy: http://pluggy.readthedocs.io/en/latest/#first-result-only + """ + + +@hookspec +def tox_runtest_pre(venv): + """ [experimental] perform arbitrary action before running tests for this venv. + + This could be used to indicate that tests for a given venv have started, for instance. + """ + + +@hookspec(firstresult=True) +def tox_runtest(venv, redirect): + """ [experimental] run the tests for this venv. + + .. note:: This hook uses ``firstresult=True`` (see pluggy_) -- hooks + implementing this will be run until one returns non-``None``. + """ + + +@hookspec +def tox_runtest_post(venv): + """ [experimental] perform arbitrary action after running tests for this venv. + + This could be used to have per-venv test reporting of pass/fail status. + """ + + +@hookspec(firstresult=True) +def tox_runenvreport(venv, action): + """ [experimental] Get the installed packages and versions in this venv + + This could be used for alternative (ie non-pip) package managers, this + plugin should return a ``list`` of type ``str`` + """ diff --git a/robot/lib/python3.8/site-packages/tox/interpreters.py b/robot/lib/python3.8/site-packages/tox/interpreters.py new file mode 100644 index 0000000000000000000000000000000000000000..c35b9bfa75ed884d7e62968d2b2a2a2c0f71c3c8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox/interpreters.py @@ -0,0 +1,186 @@ +import distutils.util +import inspect +import re +import subprocess +import sys + +import py + +from tox import hookimpl + + +class Interpreters: + def __init__(self, hook): + self.name2executable = {} + self.executable2info = {} + self.hook = hook + + def get_executable(self, envconfig): + """ return path object to the executable for the given + name (e.g. python2.7, python3.6, python etc.) + if name is already an existing path, return name. + If an interpreter cannot be found, return None. + """ + try: + return self.name2executable[envconfig.envname] + except KeyError: + exe = self.hook.tox_get_python_executable(envconfig=envconfig) + self.name2executable[envconfig.envname] = exe + return exe + + def get_info(self, envconfig): + executable = self.get_executable(envconfig) + name = envconfig.basepython + if not executable: + return NoInterpreterInfo(name=name) + try: + return self.executable2info[executable] + except KeyError: + info = run_and_get_interpreter_info(name, executable) + self.executable2info[executable] = info + return info + + def get_sitepackagesdir(self, info, envdir): + if not info.executable: + return "" + envdir = str(envdir) + try: + res = exec_on_interpreter(info.executable, + [inspect.getsource(sitepackagesdir), + "print(sitepackagesdir(%r))" % envdir]) + except ExecFailed as e: + print("execution failed: %s -- %s" % (e.out, e.err)) + return "" + else: + return res["dir"] + + +def run_and_get_interpreter_info(name, executable): + assert executable + try: + result = exec_on_interpreter(executable, [inspect.getsource(pyinfo), + "print(pyinfo())"]) + except ExecFailed as e: + return NoInterpreterInfo(name, executable=e.executable, + out=e.out, err=e.err) + else: + return InterpreterInfo(name, executable, **result) + + +def exec_on_interpreter(executable, source): + if isinstance(source, list): + source = "\n".join(source) + from subprocess import Popen, PIPE + args = [str(executable)] + popen = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) + popen.stdin.write(source.encode("utf8")) + out, err = popen.communicate() + if popen.returncode: + raise ExecFailed(executable, source, out, err) + try: + result = eval(out.strip()) + except Exception: + raise ExecFailed(executable, source, out, + "could not decode %r" % out) + return result + + +class ExecFailed(Exception): + def __init__(self, executable, source, out, err): + self.executable = executable + self.source = source + self.out = out + self.err = err + + +class InterpreterInfo: + runnable = True + + def __init__(self, name, executable, version_info, sysplatform): + assert executable and version_info + self.name = name + self.executable = executable + self.version_info = version_info + self.sysplatform = sysplatform + + def __str__(self): + return "" % ( + self.executable, self.version_info) + + +class NoInterpreterInfo: + runnable = False + + def __init__(self, name, executable=None, + out=None, err="not found"): + self.name = name + self.executable = executable + self.version_info = None + self.out = out + self.err = err + + def __str__(self): + if self.executable: + return "" % self.executable + else: + return "" % self.name + + +if sys.platform != "win32": + @hookimpl + def tox_get_python_executable(envconfig): + return py.path.local.sysfind(envconfig.basepython) + +else: + @hookimpl + def tox_get_python_executable(envconfig): + name = envconfig.basepython + p = py.path.local.sysfind(name) + if p: + return p + actual = None + # Is this a standard PythonX.Y name? + m = re.match(r"python(\d)\.(\d)", name) + if m: + # The standard names are in predictable places. + actual = r"c:\python%s%s\python.exe" % m.groups() + if not actual: + actual = win32map.get(name, None) + if actual: + actual = py.path.local(actual) + if actual.check(): + return actual + # The standard executables can be found as a last resort via the + # Python launcher py.exe + if m: + return locate_via_py(*m.groups()) + + # Exceptions to the usual windows mapping + win32map = { + 'python': sys.executable, + 'jython': r"c:\jython2.5.1\jython.bat", + } + + def locate_via_py(v_maj, v_min): + ver = "-%s.%s" % (v_maj, v_min) + script = "import sys; print(sys.executable)" + py_exe = distutils.spawn.find_executable('py') + if py_exe: + proc = subprocess.Popen( + (py_exe, ver, '-c', script), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, _ = proc.communicate() + if not proc.returncode: + return out.decode('UTF-8').strip() + + +def pyinfo(): + import sys + return {"version_info": tuple(sys.version_info), + "sysplatform": sys.platform} + + +def sitepackagesdir(envdir): + import distutils.sysconfig + return {"dir": distutils.sysconfig.get_python_lib(prefix=envdir)} diff --git a/robot/lib/python3.8/site-packages/tox/result.py b/robot/lib/python3.8/site-packages/tox/result.py new file mode 100644 index 0000000000000000000000000000000000000000..7aba9dc79c45723bdf99e4a08865a4f6c71bfb62 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox/result.py @@ -0,0 +1,84 @@ +import json +import socket +import sys + +import py + +from tox import __version__ as toxver + + +class ResultLog: + + def __init__(self, dict=None): + if dict is None: + dict = {} + self.dict = dict + self.dict.update({"reportversion": "1", "toxversion": toxver}) + self.dict["platform"] = sys.platform + self.dict["host"] = socket.getfqdn() + + def set_header(self, installpkg): + """ + :param py.path.local installpkg: Path ot the package. + """ + self.dict["installpkg"] = { + "md5": installpkg.computehash("md5"), + "sha256": installpkg.computehash("sha256"), + "basename": installpkg.basename, + } + + def get_envlog(self, name): + testenvs = self.dict.setdefault("testenvs", {}) + d = testenvs.setdefault(name, {}) + return EnvLog(self, name, d) + + def dumps_json(self): + return json.dumps(self.dict, indent=2) + + @classmethod + def loads_json(cls, data): + return cls(json.loads(data)) + + +class EnvLog: + def __init__(self, reportlog, name, dict): + self.reportlog = reportlog + self.name = name + self.dict = dict + + def set_python_info(self, pythonexecutable): + pythonexecutable = py.path.local(pythonexecutable) + out = pythonexecutable.sysexec("-c", + "import sys; " + "print(sys.executable);" + "print(list(sys.version_info)); " + "print(sys.version)") + lines = out.splitlines() + executable = lines.pop(0) + version_info = eval(lines.pop(0)) + version = "\n".join(lines) + self.dict["python"] = { + "executable": executable, + "version_info": version_info, + "version": version, + } + + def get_commandlog(self, name): + return CommandLog(self, self.dict.setdefault(name, [])) + + def set_installed(self, packages): + self.dict["installed_packages"] = packages + + +class CommandLog: + def __init__(self, envlog, list): + self.envlog = envlog + self.list = list + + def add_command(self, argv, output, retcode): + d = {} + self.list.append(d) + d["command"] = argv + d["output"] = output + d["retcode"] = str(retcode) + return d diff --git a/robot/lib/python3.8/site-packages/tox/session.py b/robot/lib/python3.8/site-packages/tox/session.py new file mode 100644 index 0000000000000000000000000000000000000000..7f013e451b78b404b946d8af8da7cc5a23f78a53 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox/session.py @@ -0,0 +1,771 @@ +""" +Automatically package and test a Python project against configurable +Python2 and Python3 based virtual environments. Environments are +setup by using virtualenv. Configuration is generally done through an +INI-style "tox.ini" file. +""" +from __future__ import print_function + +import os +import re +import shutil +import subprocess +import sys +import time + +import py + +import tox +from tox._verlib import IrrationalVersionError +from tox._verlib import NormalizedVersion +from tox.config import parseconfig +from tox.result import ResultLog +from tox.venv import VirtualEnv + + +def prepare(args): + config = parseconfig(args) + if config.option.help: + show_help(config) + raise SystemExit(0) + elif config.option.helpini: + show_help_ini(config) + raise SystemExit(0) + return config + + +def run_main(args=None): + if args is None: + args = sys.argv[1:] + main(args) + + +def main(args): + try: + config = prepare(args) + retcode = Session(config).runcommand() + if retcode is None: + retcode = 0 + raise SystemExit(retcode) + except KeyboardInterrupt: + raise SystemExit(2) + except tox.exception.MinVersionError as e: + r = Reporter(None) + r.error(str(e)) + raise SystemExit(1) + + +def show_help(config): + tw = py.io.TerminalWriter() + tw.write(config._parser._format_help()) + tw.line() + tw.line("Environment variables", bold=True) + tw.line("TOXENV: comma separated list of environments " + "(overridable by '-e')") + tw.line("TOX_TESTENV_PASSENV: space-separated list of extra " + "environment variables to be passed into test command " + "environments") + + +def show_help_ini(config): + tw = py.io.TerminalWriter() + tw.sep("-", "per-testenv attributes") + for env_attr in config._testenv_attr: + tw.line("%-15s %-8s default: %s" % + (env_attr.name, "<" + env_attr.type + ">", env_attr.default), bold=True) + tw.line(env_attr.help) + tw.line() + + +class Action(object): + def __init__(self, session, venv, msg, args): + self.venv = venv + self.msg = msg + self.activity = msg.split(" ", 1)[0] + self.session = session + self.report = session.report + self.args = args + self.id = venv and venv.envconfig.envname or "tox" + self._popenlist = [] + if self.venv: + self.venvname = self.venv.name + else: + self.venvname = "GLOB" + if msg == "runtests": + cat = "test" + else: + cat = "setup" + envlog = session.resultlog.get_envlog(self.venvname) + self.commandlog = envlog.get_commandlog(cat) + + def __enter__(self): + self.report.logaction_start(self) + + def __exit__(self, *args): + self.report.logaction_finish(self) + + def setactivity(self, name, msg): + self.activity = name + if msg: + self.report.verbosity0("%s %s: %s" % (self.venvname, name, msg), bold=True) + else: + self.report.verbosity1("%s %s: %s" % (self.venvname, name, msg), bold=True) + + def info(self, name, msg): + self.report.verbosity1("%s %s: %s" % (self.venvname, name, msg), bold=True) + + def _initlogpath(self, actionid): + if self.venv: + logdir = self.venv.envconfig.envlogdir + else: + logdir = self.session.config.logdir + try: + log_count = len(logdir.listdir("%s-*" % actionid)) + except (py.error.ENOENT, py.error.ENOTDIR): + logdir.ensure(dir=1) + log_count = 0 + path = logdir.join("%s-%s.log" % (actionid, log_count)) + f = path.open('w') + f.flush() + return f + + def popen(self, args, cwd=None, env=None, redirect=True, returnout=False, ignore_ret=False): + stdout = outpath = None + resultjson = self.session.config.option.resultjson + if resultjson or redirect: + fout = self._initlogpath(self.id) + fout.write("actionid: %s\nmsg: %s\ncmdargs: %r\n\n" % (self.id, self.msg, args)) + fout.flush() + outpath = py.path.local(fout.name) + fin = outpath.open('rb') + fin.read() # read the header, so it won't be written to stdout + stdout = fout + elif returnout: + stdout = subprocess.PIPE + if cwd is None: + # XXX cwd = self.session.config.cwd + cwd = py.path.local() + try: + popen = self._popen(args, cwd, env=env, + stdout=stdout, stderr=subprocess.STDOUT) + except OSError as e: + self.report.error("invocation failed (errno %d), args: %s, cwd: %s" % + (e.errno, args, cwd)) + raise + popen.outpath = outpath + popen.args = [str(x) for x in args] + popen.cwd = cwd + popen.action = self + self._popenlist.append(popen) + try: + self.report.logpopen(popen, env=env) + try: + if resultjson and not redirect: + if popen.stderr is not None: + # prevent deadlock + raise ValueError("stderr must not be piped here") + # we read binary from the process and must write using a + # binary stream + buf = getattr(sys.stdout, 'buffer', sys.stdout) + out = None + last_time = time.time() + while 1: + # we have to read one byte at a time, otherwise there + # might be no output for a long time with slow tests + data = fin.read(1) + if data: + buf.write(data) + if b'\n' in data or (time.time() - last_time) > 1: + # we flush on newlines or after 1 second to + # provide quick enough feedback to the user + # when printing a dot per test + buf.flush() + last_time = time.time() + elif popen.poll() is not None: + if popen.stdout is not None: + popen.stdout.close() + break + else: + time.sleep(0.1) + # the seek updates internal read buffers + fin.seek(0, 1) + fin.close() + else: + out, err = popen.communicate() + except KeyboardInterrupt: + self.report.keyboard_interrupt() + popen.wait() + raise + ret = popen.wait() + finally: + self._popenlist.remove(popen) + if ret and not ignore_ret: + invoked = " ".join(map(str, popen.args)) + if outpath: + self.report.error("invocation failed (exit code %d), logfile: %s" % + (ret, outpath)) + out = outpath.read() + self.report.error(out) + if hasattr(self, "commandlog"): + self.commandlog.add_command(popen.args, out, ret) + raise tox.exception.InvocationError( + "%s (see %s)" % (invoked, outpath), ret) + else: + raise tox.exception.InvocationError("%r" % (invoked,), ret) + if not out and outpath: + out = outpath.read() + if hasattr(self, "commandlog"): + self.commandlog.add_command(popen.args, out, ret) + return out + + def _rewriteargs(self, cwd, args): + newargs = [] + for arg in args: + if sys.platform != "win32" and isinstance(arg, py.path.local): + arg = cwd.bestrelpath(arg) + newargs.append(str(arg)) + + # subprocess does not always take kindly to .py scripts + # so adding the interpreter here + if sys.platform == "win32": + ext = os.path.splitext(str(newargs[0]))[1].lower() + if ext == '.py' and self.venv: + newargs = [str(self.venv.envconfig.envpython)] + newargs + + return newargs + + def _popen(self, args, cwd, stdout, stderr, env=None): + args = self._rewriteargs(cwd, args) + if env is None: + env = os.environ.copy() + return self.session.popen(args, shell=False, cwd=str(cwd), + universal_newlines=True, + stdout=stdout, stderr=stderr, env=env) + + +class Verbosity(object): + DEBUG = 2 + INFO = 1 + DEFAULT = 0 + QUIET = -1 + EXTRA_QUIET = -2 + + +class Reporter(object): + actionchar = "-" + + def __init__(self, session): + self.tw = py.io.TerminalWriter() + self.session = session + self._reportedlines = [] + + @property + def verbosity(self): + if self.session: + return (self.session.config.option.verbose_level - + self.session.config.option.quiet_level) + else: + return Verbosity.DEBUG + + def logpopen(self, popen, env): + """ log information about the action.popen() created process. """ + cmd = " ".join(map(str, popen.args)) + if popen.outpath: + self.verbosity1(" %s$ %s >%s" % (popen.cwd, cmd, popen.outpath)) + else: + self.verbosity1(" %s$ %s " % (popen.cwd, cmd)) + + def logaction_start(self, action): + msg = action.msg + " " + " ".join(map(str, action.args)) + self.verbosity2("%s start: %s" % (action.venvname, msg), bold=True) + assert not hasattr(action, "_starttime") + action._starttime = time.time() + + def logaction_finish(self, action): + duration = time.time() - action._starttime + self.verbosity2("%s finish: %s after %.2f seconds" % ( + action.venvname, action.msg, duration), bold=True) + delattr(action, '_starttime') + + def startsummary(self): + if self.verbosity >= Verbosity.QUIET: + self.tw.sep("_", "summary") + + def info(self, msg): + if self.verbosity >= Verbosity.DEBUG: + self.logline(msg) + + def using(self, msg): + if self.verbosity >= 1: + self.logline("using %s" % (msg,), bold=True) + + def keyboard_interrupt(self): + self.error("KEYBOARDINTERRUPT") + + def keyvalue(self, name, value): + if name.endswith(":"): + name += " " + self.tw.write(name, bold=True) + self.tw.write(value) + self.tw.line() + + def line(self, msg, **opts): + self.logline(msg, **opts) + + def good(self, msg): + self.logline(msg, green=True) + + def warning(self, msg): + if self.verbosity >= Verbosity.QUIET: + self.logline("WARNING:" + msg, red=True) + + def error(self, msg): + if self.verbosity >= Verbosity.QUIET: + self.logline("ERROR: " + msg, red=True) + + def skip(self, msg): + if self.verbosity >= Verbosity.QUIET: + self.logline("SKIPPED:" + msg, yellow=True) + + def logline(self, msg, **opts): + self._reportedlines.append(msg) + self.tw.line("%s" % msg, **opts) + + def verbosity0(self, msg, **opts): + if self.verbosity >= Verbosity.DEFAULT: + self.logline("%s" % msg, **opts) + + def verbosity1(self, msg, **opts): + if self.verbosity >= Verbosity.INFO: + self.logline("%s" % msg, **opts) + + def verbosity2(self, msg, **opts): + if self.verbosity >= Verbosity.DEBUG: + self.logline("%s" % msg, **opts) + + # def log(self, msg): + # print(msg, file=sys.stderr) + + +class Session: + """ (unstable API). the session object that ties + together configuration, reporting, venv creation, testing. """ + + def __init__(self, config, popen=subprocess.Popen, Report=Reporter): + self.config = config + self.popen = popen + self.resultlog = ResultLog() + self.report = Report(self) + self.make_emptydir(config.logdir) + config.logdir.ensure(dir=1) + self.report.using("tox.ini: %s" % (self.config.toxinipath,)) + self._spec2pkg = {} + self._name2venv = {} + try: + self.venvlist = [ + self.getvenv(x) + for x in self.config.envlist + ] + except LookupError: + raise SystemExit(1) + except tox.exception.ConfigError as e: + self.report.error(str(e)) + raise SystemExit(1) + self._actions = [] + + @property + def hook(self): + return self.config.pluginmanager.hook + + def _makevenv(self, name): + envconfig = self.config.envconfigs.get(name, None) + if envconfig is None: + self.report.error("unknown environment %r" % name) + raise LookupError(name) + elif envconfig.envdir == self.config.toxinidir: + self.report.error( + "venv %r in %s would delete project" % (name, envconfig.envdir)) + raise tox.exception.ConfigError('envdir must not equal toxinidir') + venv = VirtualEnv(envconfig=envconfig, session=self) + self._name2venv[name] = venv + return venv + + def getvenv(self, name): + """ return a VirtualEnv controler object for the 'name' env. """ + try: + return self._name2venv[name] + except KeyError: + return self._makevenv(name) + + def newaction(self, venv, msg, *args): + action = Action(self, venv, msg, args) + self._actions.append(action) + return action + + def runcommand(self): + self.report.using("tox-%s from %s" % (tox.__version__, tox.__file__)) + verbosity = (self.report.verbosity > Verbosity.DEFAULT) + if self.config.option.showconfig: + self.showconfig() + elif self.config.option.listenvs: + self.showenvs(all_envs=False, description=verbosity) + elif self.config.option.listenvs_all: + self.showenvs(all_envs=True, description=verbosity) + else: + return self.subcommand_test() + + def _copyfiles(self, srcdir, pathlist, destdir): + for relpath in pathlist: + src = srcdir.join(relpath) + if not src.check(): + self.report.error("missing source file: %s" % (src,)) + raise SystemExit(1) + target = destdir.join(relpath) + target.dirpath().ensure(dir=1) + src.copy(target) + + def _makesdist(self): + setup = self.config.setupdir.join("setup.py") + if not setup.check(): + self.report.error( + "No setup.py file found. The expected location is:\n" + " %s\n" + "You can\n" + " 1. Create one:\n" + " https://packaging.python.org/tutorials/distributing-packages/#setup-py\n" + " 2. Configure tox to avoid running sdist:\n" + " http://tox.readthedocs.io/en/latest/example/general.html" + "#avoiding-expensive-sdist" % setup + ) + raise SystemExit(1) + action = self.newaction(None, "packaging") + with action: + action.setactivity("sdist-make", setup) + self.make_emptydir(self.config.distdir) + action.popen([sys.executable, setup, "sdist", "--formats=zip", + "--dist-dir", self.config.distdir, ], + cwd=self.config.setupdir) + try: + return self.config.distdir.listdir()[0] + except py.error.ENOENT: + # check if empty or comment only + data = [] + with open(str(setup)) as fp: + for line in fp: + if line and line[0] == '#': + continue + data.append(line) + if not ''.join(data).strip(): + self.report.error( + 'setup.py is empty' + ) + raise SystemExit(1) + self.report.error( + 'No dist directory found. Please check setup.py, e.g with:\n' + ' python setup.py sdist' + ) + raise SystemExit(1) + + def make_emptydir(self, path): + if path.check(): + self.report.info(" removing %s" % path) + shutil.rmtree(str(path), ignore_errors=True) + path.ensure(dir=1) + + def setupenv(self, venv): + if venv.envconfig.missing_subs: + venv.status = ( + "unresolvable substitution(s): %s. " + "Environment variables are missing or defined recursively." % + (','.join(["'%s'" % m for m in venv.envconfig.missing_subs]))) + return + if not venv.matching_platform(): + venv.status = "platform mismatch" + return # we simply omit non-matching platforms + action = self.newaction(venv, "getenv", venv.envconfig.envdir) + with action: + venv.status = 0 + default_ret_code = 1 + envlog = self.resultlog.get_envlog(venv.name) + try: + status = venv.update(action=action) + except IOError as e: + if e.args[0] != 2: + raise + status = ( + "Error creating virtualenv. Note that spaces in paths are " + "not supported by virtualenv. Error details: %r" % e) + except tox.exception.InvocationError as e: + status = ( + "Error creating virtualenv. Note that some special " + "characters (e.g. ':' and unicode symbols) in paths are " + "not supported by virtualenv. Error details: %r" % e) + except tox.exception.InterpreterNotFound as e: + status = e + if self.config.option.skip_missing_interpreters: + default_ret_code = 0 + if status: + commandlog = envlog.get_commandlog("setup") + commandlog.add_command(["setup virtualenv"], str(status), default_ret_code) + venv.status = status + if default_ret_code == 0: + self.report.skip(str(status)) + else: + self.report.error(str(status)) + return False + commandpath = venv.getcommandpath("python") + envlog.set_python_info(commandpath) + return True + + def finishvenv(self, venv): + action = self.newaction(venv, "finishvenv") + with action: + venv.finish() + return True + + def developpkg(self, venv, setupdir): + action = self.newaction(venv, "developpkg", setupdir) + with action: + try: + venv.developpkg(setupdir, action) + return True + except tox.exception.InvocationError: + venv.status = sys.exc_info()[1] + return False + + def installpkg(self, venv, path): + """Install package in the specified virtual environment. + + :param VenvConfig venv: Destination environment + :param str path: Path to the distribution package. + :return: True if package installed otherwise False. + :rtype: bool + """ + self.resultlog.set_header(installpkg=py.path.local(path)) + action = self.newaction(venv, "installpkg", path) + with action: + try: + venv.installpkg(path, action) + return True + except tox.exception.InvocationError: + venv.status = sys.exc_info()[1] + return False + + def get_installpkg_path(self): + """ + :return: Path to the distribution + :rtype: py.path.local + """ + if not self.config.option.sdistonly and (self.config.sdistsrc or + self.config.option.installpkg): + path = self.config.option.installpkg + if not path: + path = self.config.sdistsrc + path = self._resolve_pkg(path) + self.report.info("using package %r, skipping 'sdist' activity " % + str(path)) + else: + try: + path = self._makesdist() + except tox.exception.InvocationError: + v = sys.exc_info()[1] + self.report.error("FAIL could not package project - v = %r" % + v) + return + sdistfile = self.config.distshare.join(path.basename) + if sdistfile != path: + self.report.info("copying new sdistfile to %r" % + str(sdistfile)) + try: + sdistfile.dirpath().ensure(dir=1) + except py.error.Error: + self.report.warning("could not copy distfile to %s" % + sdistfile.dirpath()) + else: + path.copy(sdistfile) + return path + + def subcommand_test(self): + if self.config.skipsdist: + self.report.info("skipping sdist step") + path = None + else: + path = self.get_installpkg_path() + if not path: + return 2 + if self.config.option.sdistonly: + return + for venv in self.venvlist: + if self.setupenv(venv): + if venv.envconfig.skip_install: + self.finishvenv(venv) + else: + if venv.envconfig.usedevelop: + self.developpkg(venv, self.config.setupdir) + elif self.config.skipsdist: + self.finishvenv(venv) + else: + self.installpkg(venv, path) + + self.runenvreport(venv) + self.runtestenv(venv) + retcode = self._summary() + return retcode + + def runenvreport(self, venv): + """ + Run an environment report to show which package + versions are installed in the venv + """ + action = self.newaction(venv, "envreport") + with action: + packages = self.hook.tox_runenvreport(venv=venv, action=action) + action.setactivity("installed", ",".join(packages)) + envlog = self.resultlog.get_envlog(venv.name) + envlog.set_installed(packages) + + def runtestenv(self, venv, redirect=False): + if not self.config.option.notest: + if venv.status: + return + self.hook.tox_runtest_pre(venv=venv) + self.hook.tox_runtest(venv=venv, redirect=redirect) + self.hook.tox_runtest_post(venv=venv) + else: + venv.status = "skipped tests" + + def _summary(self): + self.report.startsummary() + retcode = 0 + for venv in self.venvlist: + status = venv.status + if isinstance(status, tox.exception.InterpreterNotFound): + msg = " %s: %s" % (venv.envconfig.envname, str(status)) + if self.config.option.skip_missing_interpreters: + self.report.skip(msg) + else: + retcode = 1 + self.report.error(msg) + elif status == "platform mismatch": + msg = " %s: %s" % (venv.envconfig.envname, str(status)) + self.report.skip(msg) + elif status and status == "ignored failed command": + msg = " %s: %s" % (venv.envconfig.envname, str(status)) + self.report.good(msg) + elif status and status != "skipped tests": + msg = " %s: %s" % (venv.envconfig.envname, str(status)) + self.report.error(msg) + retcode = 1 + else: + if not status: + status = "commands succeeded" + self.report.good(" %s: %s" % (venv.envconfig.envname, status)) + if not retcode: + self.report.good(" congratulations :)") + + path = self.config.option.resultjson + if path: + path = py.path.local(path) + path.write(self.resultlog.dumps_json()) + self.report.line("wrote json report at: %s" % path) + return retcode + + def showconfig(self): + self.info_versions() + self.report.keyvalue("config-file:", self.config.option.configfile) + self.report.keyvalue("toxinipath: ", self.config.toxinipath) + self.report.keyvalue("toxinidir: ", self.config.toxinidir) + self.report.keyvalue("toxworkdir: ", self.config.toxworkdir) + self.report.keyvalue("setupdir: ", self.config.setupdir) + self.report.keyvalue("distshare: ", self.config.distshare) + self.report.keyvalue("skipsdist: ", self.config.skipsdist) + self.report.tw.line() + for envconfig in self.config.envconfigs.values(): + self.report.line("[testenv:%s]" % envconfig.envname, bold=True) + for attr in self.config._parser._testenv_attr: + self.report.line(" %-15s = %s" + % (attr.name, getattr(envconfig, attr.name))) + + def showenvs(self, all_envs=False, description=False): + env_conf = self.config.envconfigs # this contains all environments + default = self.config.envlist # this only the defaults + extra = sorted(e for e in env_conf if e not in default) if all_envs else [] + if description: + self.report.line('default environments:') + max_length = max(len(env) for env in (default + extra)) + + def report_env(e): + if description: + text = env_conf[e].description or '[no description]' + msg = '{} -> {}'.format(e.ljust(max_length), text).strip() + else: + msg = e + self.report.line(msg) + + for e in default: + report_env(e) + if all_envs and extra: + if description: + self.report.line('') + self.report.line('additional environments:') + for e in extra: + report_env(e) + + def info_versions(self): + versions = ['tox-%s' % tox.__version__] + proc = subprocess.Popen( + (sys.executable, '-m', 'virtualenv', '--version'), + stdout=subprocess.PIPE, + ) + out, _ = proc.communicate() + versions.append('virtualenv-{}'.format(out.decode('UTF-8').strip())) + self.report.keyvalue("tool-versions:", " ".join(versions)) + + def _resolve_pkg(self, pkgspec): + try: + return self._spec2pkg[pkgspec] + except KeyError: + self._spec2pkg[pkgspec] = x = self._resolvepkg(pkgspec) + return x + + def _resolvepkg(self, pkgspec): + if not os.path.isabs(str(pkgspec)): + return pkgspec + p = py.path.local(pkgspec) + if p.check(): + return p + if not p.dirpath().check(dir=1): + raise tox.exception.MissingDirectory(p.dirpath()) + self.report.info("determining %s" % p) + candidates = p.dirpath().listdir(p.basename) + if len(candidates) == 0: + raise tox.exception.MissingDependency(pkgspec) + if len(candidates) > 1: + items = [] + for x in candidates: + ver = getversion(x.basename) + if ver is not None: + items.append((ver, x)) + else: + self.report.warning("could not determine version of: %s" % + str(x)) + items.sort() + if not items: + raise tox.exception.MissingDependency(pkgspec) + return items[-1][1] + else: + return candidates[0] + + +_rex_getversion = re.compile(r"[\w_\-\+\.]+-(.*)\.(zip|tar\.gz)") + + +def getversion(basename): + m = _rex_getversion.match(basename) + if m is None: + return None + version = m.group(1) + try: + return NormalizedVersion(version) + except IrrationalVersionError: + return None diff --git a/robot/lib/python3.8/site-packages/tox/venv.py b/robot/lib/python3.8/site-packages/tox/venv.py new file mode 100644 index 0000000000000000000000000000000000000000..c8e205251f18a816bad868fb6d41ac295302bfa4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tox/venv.py @@ -0,0 +1,475 @@ +import ast +import codecs +import os +import re +import sys + +import py + +import tox +from .config import DepConfig +from .config import hookimpl + + +class CreationConfig: + def __init__(self, md5, python, version, sitepackages, + usedevelop, deps, alwayscopy): + self.md5 = md5 + self.python = python + self.version = version + self.sitepackages = sitepackages + self.usedevelop = usedevelop + self.alwayscopy = alwayscopy + self.deps = deps + + def writeconfig(self, path): + lines = ["%s %s" % (self.md5, self.python)] + lines.append("%s %d %d %d" % (self.version, self.sitepackages, + self.usedevelop, self.alwayscopy)) + for dep in self.deps: + lines.append("%s %s" % dep) + path.ensure() + path.write("\n".join(lines)) + + @classmethod + def readconfig(cls, path): + try: + lines = path.readlines(cr=0) + value = lines.pop(0).split(None, 1) + md5, python = value + version, sitepackages, usedevelop, alwayscopy = lines.pop(0).split(None, 4) + sitepackages = bool(int(sitepackages)) + usedevelop = bool(int(usedevelop)) + alwayscopy = bool(int(alwayscopy)) + deps = [] + for line in lines: + md5, depstring = line.split(None, 1) + deps.append((md5, depstring)) + return CreationConfig(md5, python, version, sitepackages, usedevelop, deps, alwayscopy) + except Exception: + return None + + def matches(self, other): + return (other and self.md5 == other.md5 and + self.python == other.python and + self.version == other.version and + self.sitepackages == other.sitepackages and + self.usedevelop == other.usedevelop and + self.alwayscopy == other.alwayscopy and + self.deps == other.deps) + + +class VirtualEnv(object): + def __init__(self, envconfig=None, session=None): + self.envconfig = envconfig + self.session = session + + @property + def hook(self): + return self.envconfig.config.pluginmanager.hook + + @property + def path(self): + """ Path to environment base dir. """ + return self.envconfig.envdir + + @property + def path_config(self): + return self.path.join(".tox-config1") + + @property + def name(self): + """ test environment name. """ + return self.envconfig.envname + + def __repr__(self): + return "" % (self.path) + + def getcommandpath(self, name, venv=True, cwd=None): + """ Return absolute path (str or localpath) for specified command name. + + - If it's a local path we will rewrite it as as a relative path. + - If venv is True we will check if the command is coming from the venv + or is whitelisted to come from external. + """ + name = str(name) + if os.path.isabs(name): + return name + if os.path.split(name)[0] == ".": + path = cwd.join(name) + if path.check(): + return str(path) + + if venv: + path = self._venv_lookup_and_check_external_whitelist(name) + else: + path = self._normal_lookup(name) + + if path is None: + raise tox.exception.InvocationError( + "could not find executable %r" % (name,)) + + return str(path) # will not be rewritten for reporting + + def _venv_lookup_and_check_external_whitelist(self, name): + path = self._venv_lookup(name) + if path is None: + path = self._normal_lookup(name) + if path is not None: + self._check_external_allowed_and_warn(path) + return path + + def _venv_lookup(self, name): + return py.path.local.sysfind(name, paths=[self.envconfig.envbindir]) + + def _normal_lookup(self, name): + return py.path.local.sysfind(name) + + def _check_external_allowed_and_warn(self, path): + if not self.is_allowed_external(path): + self.session.report.warning( + "test command found but not installed in testenv\n" + " cmd: %s\n" + " env: %s\n" + "Maybe you forgot to specify a dependency? " + "See also the whitelist_externals envconfig setting." % ( + path, self.envconfig.envdir)) + + def is_allowed_external(self, p): + tryadd = [""] + if sys.platform == "win32": + tryadd += [ + os.path.normcase(x) + for x in os.environ['PATHEXT'].split(os.pathsep) + ] + p = py.path.local(os.path.normcase(str(p))) + for x in self.envconfig.whitelist_externals: + for add in tryadd: + if p.fnmatch(x + add): + return True + return False + + def update(self, action): + """ return status string for updating actual venv to match configuration. + if status string is empty, all is ok. + """ + rconfig = CreationConfig.readconfig(self.path_config) + if not self.envconfig.recreate and rconfig and \ + rconfig.matches(self._getliveconfig()): + action.info("reusing", self.envconfig.envdir) + return + if rconfig is None: + action.setactivity("create", self.envconfig.envdir) + else: + action.setactivity("recreate", self.envconfig.envdir) + try: + self.hook.tox_testenv_create(action=action, venv=self) + self.just_created = True + except tox.exception.UnsupportedInterpreter: + return sys.exc_info()[1] + try: + self.hook.tox_testenv_install_deps(action=action, venv=self) + except tox.exception.InvocationError: + v = sys.exc_info()[1] + return "could not install deps %s; v = %r" % ( + self.envconfig.deps, v) + + def _getliveconfig(self): + python = self.envconfig.python_info.executable + md5 = getdigest(python) + version = tox.__version__ + sitepackages = self.envconfig.sitepackages + develop = self.envconfig.usedevelop + alwayscopy = self.envconfig.alwayscopy + deps = [] + for dep in self._getresolvedeps(): + raw_dep = dep.name + md5 = getdigest(raw_dep) + deps.append((md5, raw_dep)) + return CreationConfig(md5, python, version, + sitepackages, develop, deps, alwayscopy) + + def _getresolvedeps(self): + deps = [] + for dep in self.envconfig.deps: + if dep.indexserver is None: + res = self.session._resolve_pkg(dep.name) + if res != dep.name: + dep = dep.__class__(res) + deps.append(dep) + return deps + + def getsupportedinterpreter(self): + return self.envconfig.getsupportedinterpreter() + + def matching_platform(self): + return re.match(self.envconfig.platform, sys.platform) + + def finish(self): + self._getliveconfig().writeconfig(self.path_config) + + def _needs_reinstall(self, setupdir, action): + setup_py = setupdir.join('setup.py') + setup_cfg = setupdir.join('setup.cfg') + args = [self.envconfig.envpython, str(setup_py), '--name'] + env = self._getenv() + output = action.popen(args, cwd=setupdir, redirect=False, + returnout=True, env=env) + name = output.strip() + args = [self.envconfig.envpython, '-c', 'import sys; print(sys.path)'] + out = action.popen(args, redirect=False, returnout=True, env=env) + try: + sys_path = ast.literal_eval(out.strip()) + except SyntaxError: + sys_path = [] + egg_info_fname = '.'.join((name, 'egg-info')) + for d in reversed(sys_path): + egg_info = py.path.local(d).join(egg_info_fname) + if egg_info.check(): + break + else: + return True + return any( + conf_file.check() and conf_file.mtime() > egg_info.mtime() + for conf_file in (setup_py, setup_cfg) + ) + + def developpkg(self, setupdir, action): + assert action is not None + if getattr(self, 'just_created', False): + action.setactivity("develop-inst", setupdir) + self.finish() + extraopts = [] + else: + if not self._needs_reinstall(setupdir, action): + action.setactivity("develop-inst-noop", setupdir) + return + action.setactivity("develop-inst-nodeps", setupdir) + extraopts = ['--no-deps'] + + if action.venv.envconfig.extras: + setupdir += '[%s]' % ','.join(action.venv.envconfig.extras) + + self._install(['-e', setupdir], extraopts=extraopts, action=action) + + def installpkg(self, sdistpath, action): + assert action is not None + if getattr(self, 'just_created', False): + action.setactivity("inst", sdistpath) + self.finish() + extraopts = [] + else: + action.setactivity("inst-nodeps", sdistpath) + extraopts = ['-U', '--no-deps'] + + if action.venv.envconfig.extras: + sdistpath += '[%s]' % ','.join(action.venv.envconfig.extras) + + self._install([sdistpath], extraopts=extraopts, action=action) + + def _installopts(self, indexserver): + options = [] + if indexserver: + options += ["-i", indexserver] + if self.envconfig.pip_pre: + options.append("--pre") + return options + + def run_install_command(self, packages, action, options=()): + argv = self.envconfig.install_command[:] + i = argv.index('{packages}') + argv[i:i + 1] = packages + if '{opts}' in argv: + i = argv.index('{opts}') + argv[i:i + 1] = list(options) + + for x in ('PIP_RESPECT_VIRTUALENV', 'PIP_REQUIRE_VIRTUALENV', + '__PYVENV_LAUNCHER__'): + os.environ.pop(x, None) + + if 'PYTHONPATH' not in self.envconfig.passenv: + # If PYTHONPATH not explicitly asked for, remove it. + if 'PYTHONPATH' in os.environ: + self.session.report.warning( + "Discarding $PYTHONPATH from environment, to override " + "specify PYTHONPATH in 'passenv' in your configuration." + ) + os.environ.pop('PYTHONPATH') + + old_stdout = sys.stdout + sys.stdout = codecs.getwriter('utf8')(sys.stdout) + try: + self._pcall(argv, cwd=self.envconfig.config.toxinidir, + action=action, redirect=self.session.report.verbosity < 2) + finally: + sys.stdout = old_stdout + + def _install(self, deps, extraopts=None, action=None): + if not deps: + return + d = {} + ixservers = [] + for dep in deps: + if isinstance(dep, (str, py.path.local)): + dep = DepConfig(str(dep), None) + assert isinstance(dep, DepConfig), dep + if dep.indexserver is None: + ixserver = self.envconfig.config.indexserver['default'] + else: + ixserver = dep.indexserver + d.setdefault(ixserver, []).append(dep.name) + if ixserver not in ixservers: + ixservers.append(ixserver) + assert ixserver.url is None or isinstance(ixserver.url, str) + + for ixserver in ixservers: + packages = d[ixserver] + options = self._installopts(ixserver.url) + if extraopts: + options.extend(extraopts) + self.run_install_command(packages=packages, options=options, + action=action) + + def _getenv(self, testcommand=False): + if testcommand: + # for executing tests we construct a clean environment + env = {} + for envname in self.envconfig.passenv: + if envname in os.environ: + env[envname] = os.environ[envname] + else: + # for executing non-test commands we use the full + # invocation environment + env = os.environ.copy() + + # in any case we honor per-testenv setenv configuration + env.update(self.envconfig.setenv) + + env['VIRTUAL_ENV'] = str(self.path) + return env + + def test(self, redirect=False): + action = self.session.newaction(self, "runtests") + with action: + self.status = 0 + self.session.make_emptydir(self.envconfig.envtmpdir) + self.envconfig.envtmpdir.ensure(dir=1) + cwd = self.envconfig.changedir + env = self._getenv(testcommand=True) + # Display PYTHONHASHSEED to assist with reproducibility. + action.setactivity("runtests", "PYTHONHASHSEED=%r" % env.get('PYTHONHASHSEED')) + for i, argv in enumerate(self.envconfig.commands): + # have to make strings as _pcall changes argv[0] to a local() + # happens if the same environment is invoked twice + message = "commands[%s] | %s" % (i, ' '.join( + [str(x) for x in argv])) + action.setactivity("runtests", message) + # check to see if we need to ignore the return code + # if so, we need to alter the command line arguments + if argv[0].startswith("-"): + ignore_ret = True + if argv[0] == "-": + del argv[0] + else: + argv[0] = argv[0].lstrip("-") + else: + ignore_ret = False + + try: + self._pcall(argv, cwd=cwd, action=action, redirect=redirect, + ignore_ret=ignore_ret, testcommand=True) + except tox.exception.InvocationError as err: + if self.envconfig.ignore_outcome: + self.session.report.warning( + "command failed but result from testenv is ignored\n" + " cmd: %s" % (str(err),)) + self.status = "ignored failed command" + continue # keep processing commands + + self.session.report.error(str(err)) + self.status = "commands failed" + if not self.envconfig.ignore_errors: + break # Don't process remaining commands + except KeyboardInterrupt: + self.status = "keyboardinterrupt" + self.session.report.error(self.status) + raise + + def _pcall(self, args, cwd, venv=True, testcommand=False, + action=None, redirect=True, ignore_ret=False): + os.environ.pop('VIRTUALENV_PYTHON', None) + + cwd.ensure(dir=1) + args[0] = self.getcommandpath(args[0], venv, cwd) + env = self._getenv(testcommand=testcommand) + bindir = str(self.envconfig.envbindir) + env['PATH'] = p = os.pathsep.join([bindir, os.environ["PATH"]]) + self.session.report.verbosity2("setting PATH=%s" % p) + return action.popen(args, cwd=cwd, env=env, + redirect=redirect, ignore_ret=ignore_ret) + + +def getdigest(path): + path = py.path.local(path) + if not path.check(file=1): + return "0" * 32 + return path.computehash() + + +@hookimpl +def tox_testenv_create(venv, action): + # if self.getcommandpath("activate").dirpath().check(): + # return + config_interpreter = venv.getsupportedinterpreter() + args = [sys.executable, '-m', 'virtualenv'] + if venv.envconfig.sitepackages: + args.append('--system-site-packages') + if venv.envconfig.alwayscopy: + args.append('--always-copy') + # add interpreter explicitly, to prevent using + # default (virtualenv.ini) + args.extend(['--python', str(config_interpreter)]) + # if sys.platform == "win32": + # f, path, _ = imp.find_module("virtualenv") + # f.close() + # args[:1] = [str(config_interpreter), str(path)] + # else: + venv.session.make_emptydir(venv.path) + basepath = venv.path.dirpath() + basepath.ensure(dir=1) + args.append(venv.path.basename) + venv._pcall(args, venv=False, action=action, cwd=basepath) + # Return non-None to indicate the plugin has completed + return True + + +@hookimpl +def tox_testenv_install_deps(venv, action): + deps = venv._getresolvedeps() + if deps: + depinfo = ", ".join(map(str, deps)) + action.setactivity("installdeps", "%s" % depinfo) + venv._install(deps, action=action) + # Return non-None to indicate the plugin has completed + return True + + +@hookimpl +def tox_runtest(venv, redirect): + venv.test(redirect=redirect) + # Return non-None to indicate the plugin has completed + return True + + +@hookimpl +def tox_runenvreport(venv, action): + # write out version dependency information + args = venv.envconfig.list_dependencies_command + output = venv._pcall(args, + cwd=venv.envconfig.config.toxinidir, + action=action) + # the output contains a mime-header, skip it + output = output.split("\n\n")[-1] + packages = output.strip().split("\n") + # Return non-None to indicate the plugin has completed + return packages diff --git a/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..9be1d2fe595ad46b738cb22c44ed5eb559c29e82 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright 2011-2017 Lennart Regebro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/METADATA b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..7bafd427d5cf3b234ffac895ca8da636fa37968a --- /dev/null +++ b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/METADATA @@ -0,0 +1,326 @@ +Metadata-Version: 2.1 +Name: tzlocal +Version: 2.1 +Summary: tzinfo object for the local timezone +Home-page: https://github.com/regebro/tzlocal +Author: Lennart Regebro +Author-email: regebro@gmail.com +License: MIT +Keywords: timezone pytz +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: Unix +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Requires-Dist: pytz + +tzlocal +======= + +This Python module returns a ``tzinfo`` object with the local timezone information under Unix and Win-32. +It requires ``pytz``, and returns ``pytz`` ``tzinfo`` objects. + +This module attempts to fix a glaring hole in ``pytz``, that there is no way to +get the local timezone information, unless you know the zoneinfo name, and +under several Linux distros that's hard or impossible to figure out. + +Also, with Windows different timezone system using pytz isn't of much use +unless you separately configure the zoneinfo timezone name. + +With ``tzlocal`` you only need to call ``get_localzone()`` and you will get a +``tzinfo`` object with the local time zone info. On some Unices you will still +not get to know what the timezone name is, but you don't need that when you +have the tzinfo file. However, if the timezone name is readily available it +will be used. + + +Supported systems +----------------- + +These are the systems that are in theory supported: + + * Windows 2000 and later + + * Any unix-like system with a ``/etc/localtime`` or ``/usr/local/etc/localtime`` + +If you have one of the above systems and it does not work, it's a bug. +Please report it. + +Please note that if you getting a time zone called ``local``, this is not a bug, it's +actually the main feature of ``tzlocal``, that even if your system does NOT have a configuration file +with the zoneinfo name of your time zone, it will still work. + +You can also use ``tzlocal`` to get the name of your local timezone, but only if your system is +configured to make that possible. ``tzlocal`` looks for the timezone name in ``/etc/timezone``, ``/var/db/zoneinfo``, +``/etc/sysconfig/clock`` and ``/etc/conf.d/clock``. If your ``/etc/localtime`` is a symlink it can also extract the +name from that symlink. + +If you need the name of your local time zone, then please make sure your system is properly configured to allow that. +If it isn't configured, tzlocal will default to UTC. + +Usage +----- + +Load the local timezone: + + >>> from tzlocal import get_localzone + >>> tz = get_localzone() + >>> tz + + +Create a local datetime: + + >>> from datetime import datetime + >>> dt = tz.localize(datetime(2015, 4, 10, 7, 22)) + >>> dt + datetime.datetime(2015, 4, 10, 7, 22, tzinfo=) + +Lookup another timezone with `pytz`: + + >>> import pytz + >>> eastern = pytz.timezone('US/Eastern') + +Convert the datetime: + + >>> dt.astimezone(eastern) + datetime.datetime(2015, 4, 10, 1, 22, tzinfo=) + + +Maintainer +---------- + +* Lennart Regebro, regebro@gmail.com + +Contributors +------------ + +* Marc Van Olmen +* Benjamen Meyer +* Manuel Ebert +* Xiaokun Zhu +* Cameris +* Edward Betts +* McK KIM +* Cris Ewing +* Ayala Shachar +* Lev Maximov +* Jakub Wilk +* John Quarles +* Preston Landers +* Victor Torres +* Jean Jordaan +* Zackary Welch +* Mickaël Schoentgen +* Gabriel Corona + +(Sorry if I forgot someone) + +License +------- + +* MIT https://opensource.org/licenses/MIT + + +Changes +======= + +2.1 (2020-05-08) +---------------- + +- No changes. + + +2.1b1 (2020-02-08) +------------------ + +- The is_dst flag is wrong for Europe/Dublin on some Unix releases. + I changed to another way of determining if DST is in effect or not. + +- Added support for Python 3.7 and 3.8. Dropped 3.5 although it still works. + + +2.0.0 (2019-07-23) +------------------ + +- No differences since 2.0.0b3 + +Major differences since 1.5.1 +............................. + +- When no time zone configuration can be find, tzlocal now return UTC. + This is a major difference from 1.x, where an exception would be raised. + This change is because Docker images often have no configuration at all, + and the unix utilities will then default to UTC, so we follow that. + +- If tzlocal on Unix finds a timezone name in a /etc config file, then + tzlocal now verifies that the timezone it fouds has the same offset as + the local computer is configured with. If it doesn't, something is + configured incorrectly. (Victor Torres, regebro) + +- Get timezone via Termux `getprop` wrapper on Android. It's not officially + supported because we can't test it, but at least we make an effort. + (Jean Jordaan) + +Minor differences and bug fixes +............................... + +- Skip comment lines when parsing /etc/timezone. (Edward Betts) + +- Don't load timezone from current directory. (Gabriel Corona) + +- Now verifies that the config files actually contain something before + reading them. (Zackary Welch, regebro) + +- Got rid of a BytesWarning (Mickaël Schoentgen) + +- Now handles if config file paths exists, but are directories. + +- Moved tests out from distributions + +- Support wheels + + +1.5.1 (2017-12-01) +------------------ + +- 1.5 had a bug that slipped through testing, fixed that, + increased test coverage. + + +1.5 (2017-11-30) +---------------- + +- No longer treats macOS as special, but as a unix. + +- get_windows_info.py is renamed to update_windows_mappings.py + +- Windows mappings now also contain mappings from deprecated zoneinfo names. + (Preston-Landers, regebro) + + +1.4 (2017-04-18) +---------------- + +- I use MIT on my other projects, so relicensing. + + +1.4b1 (2017-04-14) +------------------ + +- Dropping support for Python versions nobody uses (2.5, 3.1, 3.2), adding 3.6 + Python 3.1 and 3.2 still works, 2.5 has been broken for some time. + +- Ayalash's OS X fix didn't work on Python 2.7, fixed that. + + +1.3.2 (2017-04-12) +------------------ + +- Ensure closing of subprocess on OS X (ayalash) + +- Removed unused imports (jwilk) + +- Closes stdout and stderr to get rid of ResourceWarnings (johnwquarles) + +- Updated Windows timezones (axil) + + +1.3 (2016-10-15) +---------------- + +- #34: Added support for /var/db/zoneinfo + + +1.2.2 (2016-03-02) +------------------ + +- #30: Fixed a bug on OS X. + + +1.2.1 (2016-02-28) +------------------ + +- Tests failed if TZ was set in the environment. (EdwardBetts) + +- Replaces os.popen() with subprocess.Popen() for OS X to + handle when systemsetup doesn't exist. (mckabi, cewing) + + +1.2 (2015-06-14) +---------------- + +- Systemd stores no time zone name, forcing us to look at the name of the file + that localtime symlinks to. (cameris) + + +1.1.2 (2014-10-18) +------------------ + +- Timezones that has 3 items did not work on Mac OS X. + (Marc Van Olmen) + +- Now doesn't fail if the TZ environment variable isn't an Olsen time zone. + +- Some timezones on Windows can apparently be empty (perhaps the are deleted). + Now these are ignored. + (Xiaokun Zhu) + + +1.1.1 (2014-01-29) +------------------ + +- I forgot to add Etc/UTC as an alias for Etc/GMT. + + +1.1 (2014-01-28) +---------------- + +- Adding better support for OS X. + +- Added support to map from tzdata/Olsen names to Windows names. + (Thanks to Benjamen Meyer). + + +1.0 (2013-05-29) +---------------- + +- Fixed some more cases where spaces needs replacing with underscores. + +- Better handling of misconfigured /etc/timezone. + +- Better error message on Windows if we can't find a timezone at all. + + +0.3 (2012-09-13) +---------------- + +- Windows 7 support. + +- Python 2.5 supported; because it only needed a __future__ import. + +- Python 3.3 tested, it worked. + +- Got rid of relative imports, because I don't actually like them, + so I don't know why I used them in the first place. + +- For each Windows zone, use the default zoneinfo zone, not the last one. + + +0.2 (2012-09-12) +---------------- + +- Python 3 support. + + +0.1 (2012-09-11) +---------------- + +- Initial release. + + diff --git a/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/RECORD b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..5d4aa5483135919dcb08cce83b0e664d049400bf --- /dev/null +++ b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/RECORD @@ -0,0 +1,17 @@ +tzlocal-2.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +tzlocal-2.1.dist-info/LICENSE.txt,sha256=2ZqyCa6xaq0sJckP_YPBqYHikP__dqQgoqsD4D8EG4w,1060 +tzlocal-2.1.dist-info/METADATA,sha256=CFvLexLJNXCk-hBmflVJxv7P2Izms0iDeVubwshTF1g,8227 +tzlocal-2.1.dist-info/RECORD,, +tzlocal-2.1.dist-info/WHEEL,sha256=aSdOKpzTGLLkKenfdFGiq92od_Dmr98YfEe8iw7iZoo,110 +tzlocal-2.1.dist-info/top_level.txt,sha256=QR6vZWP520waETnkotApPQPyVh9VnjoYPoAVHLK1DrE,8 +tzlocal-2.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +tzlocal/__init__.py,sha256=zOXBN5IP3Nc1gNiL8aVwHAhXAYTFfkTSDJ6VdjmifCQ,168 +tzlocal/__pycache__/__init__.cpython-38.pyc,, +tzlocal/__pycache__/unix.cpython-38.pyc,, +tzlocal/__pycache__/utils.cpython-38.pyc,, +tzlocal/__pycache__/win32.cpython-38.pyc,, +tzlocal/__pycache__/windows_tz.cpython-38.pyc,, +tzlocal/unix.py,sha256=7dFkjHfqNz4k9F_-PseJaKHCy8uHLKYckbIydpMGXo0,6062 +tzlocal/utils.py,sha256=FYqtaomESB2nQWR8cJalSLoQ9uq7QE0Sx0Hhud1kpTM,1692 +tzlocal/win32.py,sha256=GlvUX_yS1OGEkGmHvW_A3GR5Arxr6lrn0DetVcRanKg,3265 +tzlocal/windows_tz.py,sha256=J-5L3_TUGPiyg69GhqxFnAHhiHsjrZKfkgR1WbofaLk,31441 diff --git a/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/WHEEL b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..131c7a865b9b2d6588e0d5664edad6d10c3bbd64 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.33.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..cd5e9b12a4b82987a0ff1fce5edc6144d4cf40b2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/top_level.txt @@ -0,0 +1 @@ +tzlocal diff --git a/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/zip-safe b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/zip-safe new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/robot/lib/python3.8/site-packages/tzlocal-2.1.dist-info/zip-safe @@ -0,0 +1 @@ + diff --git a/robot/lib/python3.8/site-packages/tzlocal/__init__.py b/robot/lib/python3.8/site-packages/tzlocal/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c8196d66d9e2c754879e2639482000f1ffc28ab2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tzlocal/__init__.py @@ -0,0 +1,5 @@ +import sys +if sys.platform == 'win32': + from tzlocal.win32 import get_localzone, reload_localzone +else: + from tzlocal.unix import get_localzone, reload_localzone diff --git a/robot/lib/python3.8/site-packages/tzlocal/unix.py b/robot/lib/python3.8/site-packages/tzlocal/unix.py new file mode 100644 index 0000000000000000000000000000000000000000..8574965a5a927693aac6e6236db7ff261f5093ca --- /dev/null +++ b/robot/lib/python3.8/site-packages/tzlocal/unix.py @@ -0,0 +1,174 @@ +import os +import pytz +import re +import warnings + +from tzlocal import utils + +_cache_tz = None + + +def _tz_from_env(tzenv): + if tzenv[0] == ':': + tzenv = tzenv[1:] + + # TZ specifies a file + if os.path.isabs(tzenv) and os.path.exists(tzenv): + with open(tzenv, 'rb') as tzfile: + return pytz.tzfile.build_tzinfo('local', tzfile) + + # TZ specifies a zoneinfo zone. + try: + tz = pytz.timezone(tzenv) + # That worked, so we return this: + return tz + except pytz.UnknownTimeZoneError: + raise pytz.UnknownTimeZoneError( + "tzlocal() does not support non-zoneinfo timezones like %s. \n" + "Please use a timezone in the form of Continent/City") + + +def _try_tz_from_env(): + tzenv = os.environ.get('TZ') + if tzenv: + try: + return _tz_from_env(tzenv) + except pytz.UnknownTimeZoneError: + pass + + +def _get_localzone(_root='/'): + """Tries to find the local timezone configuration. + + This method prefers finding the timezone name and passing that to pytz, + over passing in the localtime file, as in the later case the zoneinfo + name is unknown. + + The parameter _root makes the function look for files like /etc/localtime + beneath the _root directory. This is primarily used by the tests. + In normal usage you call the function without parameters.""" + + tzenv = _try_tz_from_env() + if tzenv: + return tzenv + + # Are we under Termux on Android? + if os.path.exists('/system/bin/getprop'): + import subprocess + androidtz = subprocess.check_output(['getprop', 'persist.sys.timezone']).strip().decode() + return pytz.timezone(androidtz) + + # Now look for distribution specific configuration files + # that contain the timezone name. + for configfile in ('etc/timezone', 'var/db/zoneinfo'): + tzpath = os.path.join(_root, configfile) + try: + with open(tzpath, 'rb') as tzfile: + data = tzfile.read() + + # Issue #3 was that /etc/timezone was a zoneinfo file. + # That's a misconfiguration, but we need to handle it gracefully: + if data[:5] == b'TZif2': + continue + + etctz = data.strip().decode() + if not etctz: + # Empty file, skip + continue + for etctz in data.decode().splitlines(): + # Get rid of host definitions and comments: + if ' ' in etctz: + etctz, dummy = etctz.split(' ', 1) + if '#' in etctz: + etctz, dummy = etctz.split('#', 1) + if not etctz: + continue + tz = pytz.timezone(etctz.replace(' ', '_')) + if _root == '/': + # We are using a file in etc to name the timezone. + # Verify that the timezone specified there is actually used: + utils.assert_tz_offset(tz) + return tz + + except IOError: + # File doesn't exist or is a directory + continue + + # CentOS has a ZONE setting in /etc/sysconfig/clock, + # OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and + # Gentoo has a TIMEZONE setting in /etc/conf.d/clock + # We look through these files for a timezone: + + zone_re = re.compile(r'\s*ZONE\s*=\s*\"') + timezone_re = re.compile(r'\s*TIMEZONE\s*=\s*\"') + end_re = re.compile('\"') + + for filename in ('etc/sysconfig/clock', 'etc/conf.d/clock'): + tzpath = os.path.join(_root, filename) + try: + with open(tzpath, 'rt') as tzfile: + data = tzfile.readlines() + + for line in data: + # Look for the ZONE= setting. + match = zone_re.match(line) + if match is None: + # No ZONE= setting. Look for the TIMEZONE= setting. + match = timezone_re.match(line) + if match is not None: + # Some setting existed + line = line[match.end():] + etctz = line[:end_re.search(line).start()] + + # We found a timezone + tz = pytz.timezone(etctz.replace(' ', '_')) + if _root == '/': + # We are using a file in etc to name the timezone. + # Verify that the timezone specified there is actually used: + utils.assert_tz_offset(tz) + return tz + + except IOError: + # File doesn't exist or is a directory + continue + + # systemd distributions use symlinks that include the zone name, + # see manpage of localtime(5) and timedatectl(1) + tzpath = os.path.join(_root, 'etc/localtime') + if os.path.exists(tzpath) and os.path.islink(tzpath): + tzpath = os.path.realpath(tzpath) + start = tzpath.find("/")+1 + while start != 0: + tzpath = tzpath[start:] + try: + return pytz.timezone(tzpath) + except pytz.UnknownTimeZoneError: + pass + start = tzpath.find("/")+1 + + # No explicit setting existed. Use localtime + for filename in ('etc/localtime', 'usr/local/etc/localtime'): + tzpath = os.path.join(_root, filename) + + if not os.path.exists(tzpath): + continue + with open(tzpath, 'rb') as tzfile: + return pytz.tzfile.build_tzinfo('local', tzfile) + + warnings.warn('Can not find any timezone configuration, defaulting to UTC.') + return pytz.utc + +def get_localzone(): + """Get the computers configured local timezone, if any.""" + global _cache_tz + if _cache_tz is None: + _cache_tz = _get_localzone() + + return _cache_tz + + +def reload_localzone(): + """Reload the cached localzone. You need to call this if the timezone has changed.""" + global _cache_tz + _cache_tz = _get_localzone() + return _cache_tz diff --git a/robot/lib/python3.8/site-packages/tzlocal/utils.py b/robot/lib/python3.8/site-packages/tzlocal/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..5a6779903de6163c3df63bbd15540c595824a792 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tzlocal/utils.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +import time +import datetime +import calendar + + +def get_system_offset(): + """Get system's timezone offset using built-in library time. + + For the Timezone constants (altzone, daylight, timezone, and tzname), the + value is determined by the timezone rules in effect at module load time or + the last time tzset() is called and may be incorrect for times in the past. + + To keep compatibility with Windows, we're always importing time module here. + """ + + localtime = calendar.timegm(time.localtime()) + gmtime = calendar.timegm(time.gmtime()) + offset = gmtime - localtime + # We could get the localtime and gmtime on either side of a second switch + # so we check that the difference is less than one minute, because nobody + # has that small DST differences. + if abs(offset - time.altzone) < 60: + return -time.altzone + else: + return -time.timezone + + +def get_tz_offset(tz): + """Get timezone's offset using built-in function datetime.utcoffset().""" + return int(datetime.datetime.now(tz).utcoffset().total_seconds()) + + +def assert_tz_offset(tz): + """Assert that system's timezone offset equals to the timezone offset found. + + If they don't match, we probably have a misconfiguration, for example, an + incorrect timezone set in /etc/timezone file in systemd distributions.""" + tz_offset = get_tz_offset(tz) + system_offset = get_system_offset() + if tz_offset != system_offset: + msg = ('Timezone offset does not match system offset: {0} != {1}. ' + 'Please, check your config files.').format( + tz_offset, system_offset + ) + raise ValueError(msg) diff --git a/robot/lib/python3.8/site-packages/tzlocal/win32.py b/robot/lib/python3.8/site-packages/tzlocal/win32.py new file mode 100644 index 0000000000000000000000000000000000000000..fcc42a23f3f2183d5dd4ec58a731611a7fd4a03a --- /dev/null +++ b/robot/lib/python3.8/site-packages/tzlocal/win32.py @@ -0,0 +1,104 @@ +try: + import _winreg as winreg +except ImportError: + import winreg + +import pytz + +from tzlocal.windows_tz import win_tz +from tzlocal import utils + +_cache_tz = None + + +def valuestodict(key): + """Convert a registry key's values to a dictionary.""" + dict = {} + size = winreg.QueryInfoKey(key)[1] + for i in range(size): + data = winreg.EnumValue(key, i) + dict[data[0]] = data[1] + return dict + + +def get_localzone_name(): + # Windows is special. It has unique time zone names (in several + # meanings of the word) available, but unfortunately, they can be + # translated to the language of the operating system, so we need to + # do a backwards lookup, by going through all time zones and see which + # one matches. + handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + + TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" + localtz = winreg.OpenKey(handle, TZLOCALKEYNAME) + keyvalues = valuestodict(localtz) + localtz.Close() + + if 'TimeZoneKeyName' in keyvalues: + # Windows 7 (and Vista?) + + # For some reason this returns a string with loads of NUL bytes at + # least on some systems. I don't know if this is a bug somewhere, I + # just work around it. + tzkeyname = keyvalues['TimeZoneKeyName'].split('\x00', 1)[0] + else: + # Windows 2000 or XP + + # This is the localized name: + tzwin = keyvalues['StandardName'] + + # Open the list of timezones to look up the real name: + TZKEYNAME = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" + tzkey = winreg.OpenKey(handle, TZKEYNAME) + + # Now, match this value to Time Zone information + tzkeyname = None + for i in range(winreg.QueryInfoKey(tzkey)[0]): + subkey = winreg.EnumKey(tzkey, i) + sub = winreg.OpenKey(tzkey, subkey) + data = valuestodict(sub) + sub.Close() + try: + if data['Std'] == tzwin: + tzkeyname = subkey + break + except KeyError: + # This timezone didn't have proper configuration. + # Ignore it. + pass + + tzkey.Close() + handle.Close() + + if tzkeyname is None: + raise LookupError('Can not find Windows timezone configuration') + + timezone = win_tz.get(tzkeyname) + if timezone is None: + # Nope, that didn't work. Try adding "Standard Time", + # it seems to work a lot of times: + timezone = win_tz.get(tzkeyname + " Standard Time") + + # Return what we have. + if timezone is None: + raise pytz.UnknownTimeZoneError('Can not find timezone ' + tzkeyname) + + return timezone + + +def get_localzone(): + """Returns the zoneinfo-based tzinfo object that matches the Windows-configured timezone.""" + global _cache_tz + if _cache_tz is None: + _cache_tz = pytz.timezone(get_localzone_name()) + + utils.assert_tz_offset(_cache_tz) + return _cache_tz + + +def reload_localzone(): + """Reload the cached localzone. You need to call this if the timezone has changed.""" + global _cache_tz + _cache_tz = pytz.timezone(get_localzone_name()) + utils.assert_tz_offset(_cache_tz) + return _cache_tz diff --git a/robot/lib/python3.8/site-packages/tzlocal/windows_tz.py b/robot/lib/python3.8/site-packages/tzlocal/windows_tz.py new file mode 100644 index 0000000000000000000000000000000000000000..86ba807d0600be01a95ed063a684f07cf881dec7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/tzlocal/windows_tz.py @@ -0,0 +1,697 @@ +# This file is autogenerated by the update_windows_mapping.py script +# Do not edit. +win_tz = {'AUS Central Standard Time': 'Australia/Darwin', + 'AUS Eastern Standard Time': 'Australia/Sydney', + 'Afghanistan Standard Time': 'Asia/Kabul', + 'Alaskan Standard Time': 'America/Anchorage', + 'Aleutian Standard Time': 'America/Adak', + 'Altai Standard Time': 'Asia/Barnaul', + 'Arab Standard Time': 'Asia/Riyadh', + 'Arabian Standard Time': 'Asia/Dubai', + 'Arabic Standard Time': 'Asia/Baghdad', + 'Argentina Standard Time': 'America/Buenos_Aires', + 'Astrakhan Standard Time': 'Europe/Astrakhan', + 'Atlantic Standard Time': 'America/Halifax', + 'Aus Central W. Standard Time': 'Australia/Eucla', + 'Azerbaijan Standard Time': 'Asia/Baku', + 'Azores Standard Time': 'Atlantic/Azores', + 'Bahia Standard Time': 'America/Bahia', + 'Bangladesh Standard Time': 'Asia/Dhaka', + 'Belarus Standard Time': 'Europe/Minsk', + 'Bougainville Standard Time': 'Pacific/Bougainville', + 'Canada Central Standard Time': 'America/Regina', + 'Cape Verde Standard Time': 'Atlantic/Cape_Verde', + 'Caucasus Standard Time': 'Asia/Yerevan', + 'Cen. Australia Standard Time': 'Australia/Adelaide', + 'Central America Standard Time': 'America/Guatemala', + 'Central Asia Standard Time': 'Asia/Almaty', + 'Central Brazilian Standard Time': 'America/Cuiaba', + 'Central Europe Standard Time': 'Europe/Budapest', + 'Central European Standard Time': 'Europe/Warsaw', + 'Central Pacific Standard Time': 'Pacific/Guadalcanal', + 'Central Standard Time': 'America/Chicago', + 'Central Standard Time (Mexico)': 'America/Mexico_City', + 'Chatham Islands Standard Time': 'Pacific/Chatham', + 'China Standard Time': 'Asia/Shanghai', + 'Cuba Standard Time': 'America/Havana', + 'Dateline Standard Time': 'Etc/GMT+12', + 'E. Africa Standard Time': 'Africa/Nairobi', + 'E. Australia Standard Time': 'Australia/Brisbane', + 'E. Europe Standard Time': 'Europe/Chisinau', + 'E. South America Standard Time': 'America/Sao_Paulo', + 'Easter Island Standard Time': 'Pacific/Easter', + 'Eastern Standard Time': 'America/New_York', + 'Eastern Standard Time (Mexico)': 'America/Cancun', + 'Egypt Standard Time': 'Africa/Cairo', + 'Ekaterinburg Standard Time': 'Asia/Yekaterinburg', + 'FLE Standard Time': 'Europe/Kiev', + 'Fiji Standard Time': 'Pacific/Fiji', + 'GMT Standard Time': 'Europe/London', + 'GTB Standard Time': 'Europe/Bucharest', + 'Georgian Standard Time': 'Asia/Tbilisi', + 'Greenland Standard Time': 'America/Godthab', + 'Greenwich Standard Time': 'Atlantic/Reykjavik', + 'Haiti Standard Time': 'America/Port-au-Prince', + 'Hawaiian Standard Time': 'Pacific/Honolulu', + 'India Standard Time': 'Asia/Calcutta', + 'Iran Standard Time': 'Asia/Tehran', + 'Israel Standard Time': 'Asia/Jerusalem', + 'Jordan Standard Time': 'Asia/Amman', + 'Kaliningrad Standard Time': 'Europe/Kaliningrad', + 'Korea Standard Time': 'Asia/Seoul', + 'Libya Standard Time': 'Africa/Tripoli', + 'Line Islands Standard Time': 'Pacific/Kiritimati', + 'Lord Howe Standard Time': 'Australia/Lord_Howe', + 'Magadan Standard Time': 'Asia/Magadan', + 'Magallanes Standard Time': 'America/Punta_Arenas', + 'Marquesas Standard Time': 'Pacific/Marquesas', + 'Mauritius Standard Time': 'Indian/Mauritius', + 'Middle East Standard Time': 'Asia/Beirut', + 'Montevideo Standard Time': 'America/Montevideo', + 'Morocco Standard Time': 'Africa/Casablanca', + 'Mountain Standard Time': 'America/Denver', + 'Mountain Standard Time (Mexico)': 'America/Chihuahua', + 'Myanmar Standard Time': 'Asia/Rangoon', + 'N. Central Asia Standard Time': 'Asia/Novosibirsk', + 'Namibia Standard Time': 'Africa/Windhoek', + 'Nepal Standard Time': 'Asia/Katmandu', + 'New Zealand Standard Time': 'Pacific/Auckland', + 'Newfoundland Standard Time': 'America/St_Johns', + 'Norfolk Standard Time': 'Pacific/Norfolk', + 'North Asia East Standard Time': 'Asia/Irkutsk', + 'North Asia Standard Time': 'Asia/Krasnoyarsk', + 'North Korea Standard Time': 'Asia/Pyongyang', + 'Omsk Standard Time': 'Asia/Omsk', + 'Pacific SA Standard Time': 'America/Santiago', + 'Pacific Standard Time': 'America/Los_Angeles', + 'Pacific Standard Time (Mexico)': 'America/Tijuana', + 'Pakistan Standard Time': 'Asia/Karachi', + 'Paraguay Standard Time': 'America/Asuncion', + 'Qyzylorda Standard Time': 'Asia/Qyzylorda', + 'Romance Standard Time': 'Europe/Paris', + 'Russia Time Zone 10': 'Asia/Srednekolymsk', + 'Russia Time Zone 11': 'Asia/Kamchatka', + 'Russia Time Zone 3': 'Europe/Samara', + 'Russian Standard Time': 'Europe/Moscow', + 'SA Eastern Standard Time': 'America/Cayenne', + 'SA Pacific Standard Time': 'America/Bogota', + 'SA Western Standard Time': 'America/La_Paz', + 'SE Asia Standard Time': 'Asia/Bangkok', + 'Saint Pierre Standard Time': 'America/Miquelon', + 'Sakhalin Standard Time': 'Asia/Sakhalin', + 'Samoa Standard Time': 'Pacific/Apia', + 'Sao Tome Standard Time': 'Africa/Sao_Tome', + 'Saratov Standard Time': 'Europe/Saratov', + 'Singapore Standard Time': 'Asia/Singapore', + 'South Africa Standard Time': 'Africa/Johannesburg', + 'Sri Lanka Standard Time': 'Asia/Colombo', + 'Sudan Standard Time': 'Africa/Khartoum', + 'Syria Standard Time': 'Asia/Damascus', + 'Taipei Standard Time': 'Asia/Taipei', + 'Tasmania Standard Time': 'Australia/Hobart', + 'Tocantins Standard Time': 'America/Araguaina', + 'Tokyo Standard Time': 'Asia/Tokyo', + 'Tomsk Standard Time': 'Asia/Tomsk', + 'Tonga Standard Time': 'Pacific/Tongatapu', + 'Transbaikal Standard Time': 'Asia/Chita', + 'Turkey Standard Time': 'Europe/Istanbul', + 'Turks And Caicos Standard Time': 'America/Grand_Turk', + 'US Eastern Standard Time': 'America/Indianapolis', + 'US Mountain Standard Time': 'America/Phoenix', + 'UTC': 'Etc/GMT', + 'UTC+12': 'Etc/GMT-12', + 'UTC+13': 'Etc/GMT-13', + 'UTC-02': 'Etc/GMT+2', + 'UTC-08': 'Etc/GMT+8', + 'UTC-09': 'Etc/GMT+9', + 'UTC-11': 'Etc/GMT+11', + 'Ulaanbaatar Standard Time': 'Asia/Ulaanbaatar', + 'Venezuela Standard Time': 'America/Caracas', + 'Vladivostok Standard Time': 'Asia/Vladivostok', + 'Volgograd Standard Time': 'Europe/Volgograd', + 'W. Australia Standard Time': 'Australia/Perth', + 'W. Central Africa Standard Time': 'Africa/Lagos', + 'W. Europe Standard Time': 'Europe/Berlin', + 'W. Mongolia Standard Time': 'Asia/Hovd', + 'West Asia Standard Time': 'Asia/Tashkent', + 'West Bank Standard Time': 'Asia/Hebron', + 'West Pacific Standard Time': 'Pacific/Port_Moresby', + 'Yakutsk Standard Time': 'Asia/Yakutsk'} + +# Old name for the win_tz variable: +tz_names = win_tz + +tz_win = {'Africa/Abidjan': 'Greenwich Standard Time', + 'Africa/Accra': 'Greenwich Standard Time', + 'Africa/Addis_Ababa': 'E. Africa Standard Time', + 'Africa/Algiers': 'W. Central Africa Standard Time', + 'Africa/Asmera': 'E. Africa Standard Time', + 'Africa/Bamako': 'Greenwich Standard Time', + 'Africa/Bangui': 'W. Central Africa Standard Time', + 'Africa/Banjul': 'Greenwich Standard Time', + 'Africa/Bissau': 'Greenwich Standard Time', + 'Africa/Blantyre': 'South Africa Standard Time', + 'Africa/Brazzaville': 'W. Central Africa Standard Time', + 'Africa/Bujumbura': 'South Africa Standard Time', + 'Africa/Cairo': 'Egypt Standard Time', + 'Africa/Casablanca': 'Morocco Standard Time', + 'Africa/Ceuta': 'Romance Standard Time', + 'Africa/Conakry': 'Greenwich Standard Time', + 'Africa/Dakar': 'Greenwich Standard Time', + 'Africa/Dar_es_Salaam': 'E. Africa Standard Time', + 'Africa/Djibouti': 'E. Africa Standard Time', + 'Africa/Douala': 'W. Central Africa Standard Time', + 'Africa/El_Aaiun': 'Morocco Standard Time', + 'Africa/Freetown': 'Greenwich Standard Time', + 'Africa/Gaborone': 'South Africa Standard Time', + 'Africa/Harare': 'South Africa Standard Time', + 'Africa/Johannesburg': 'South Africa Standard Time', + 'Africa/Juba': 'E. Africa Standard Time', + 'Africa/Kampala': 'E. Africa Standard Time', + 'Africa/Khartoum': 'Sudan Standard Time', + 'Africa/Kigali': 'South Africa Standard Time', + 'Africa/Kinshasa': 'W. Central Africa Standard Time', + 'Africa/Lagos': 'W. Central Africa Standard Time', + 'Africa/Libreville': 'W. Central Africa Standard Time', + 'Africa/Lome': 'Greenwich Standard Time', + 'Africa/Luanda': 'W. Central Africa Standard Time', + 'Africa/Lubumbashi': 'South Africa Standard Time', + 'Africa/Lusaka': 'South Africa Standard Time', + 'Africa/Malabo': 'W. Central Africa Standard Time', + 'Africa/Maputo': 'South Africa Standard Time', + 'Africa/Maseru': 'South Africa Standard Time', + 'Africa/Mbabane': 'South Africa Standard Time', + 'Africa/Mogadishu': 'E. Africa Standard Time', + 'Africa/Monrovia': 'Greenwich Standard Time', + 'Africa/Nairobi': 'E. Africa Standard Time', + 'Africa/Ndjamena': 'W. Central Africa Standard Time', + 'Africa/Niamey': 'W. Central Africa Standard Time', + 'Africa/Nouakchott': 'Greenwich Standard Time', + 'Africa/Ouagadougou': 'Greenwich Standard Time', + 'Africa/Porto-Novo': 'W. Central Africa Standard Time', + 'Africa/Sao_Tome': 'Sao Tome Standard Time', + 'Africa/Timbuktu': 'Greenwich Standard Time', + 'Africa/Tripoli': 'Libya Standard Time', + 'Africa/Tunis': 'W. Central Africa Standard Time', + 'Africa/Windhoek': 'Namibia Standard Time', + 'America/Adak': 'Aleutian Standard Time', + 'America/Anchorage': 'Alaskan Standard Time', + 'America/Anguilla': 'SA Western Standard Time', + 'America/Antigua': 'SA Western Standard Time', + 'America/Araguaina': 'Tocantins Standard Time', + 'America/Argentina/La_Rioja': 'Argentina Standard Time', + 'America/Argentina/Rio_Gallegos': 'Argentina Standard Time', + 'America/Argentina/Salta': 'Argentina Standard Time', + 'America/Argentina/San_Juan': 'Argentina Standard Time', + 'America/Argentina/San_Luis': 'Argentina Standard Time', + 'America/Argentina/Tucuman': 'Argentina Standard Time', + 'America/Argentina/Ushuaia': 'Argentina Standard Time', + 'America/Aruba': 'SA Western Standard Time', + 'America/Asuncion': 'Paraguay Standard Time', + 'America/Atka': 'Aleutian Standard Time', + 'America/Bahia': 'Bahia Standard Time', + 'America/Bahia_Banderas': 'Central Standard Time (Mexico)', + 'America/Barbados': 'SA Western Standard Time', + 'America/Belem': 'SA Eastern Standard Time', + 'America/Belize': 'Central America Standard Time', + 'America/Blanc-Sablon': 'SA Western Standard Time', + 'America/Boa_Vista': 'SA Western Standard Time', + 'America/Bogota': 'SA Pacific Standard Time', + 'America/Boise': 'Mountain Standard Time', + 'America/Buenos_Aires': 'Argentina Standard Time', + 'America/Cambridge_Bay': 'Mountain Standard Time', + 'America/Campo_Grande': 'Central Brazilian Standard Time', + 'America/Cancun': 'Eastern Standard Time (Mexico)', + 'America/Caracas': 'Venezuela Standard Time', + 'America/Catamarca': 'Argentina Standard Time', + 'America/Cayenne': 'SA Eastern Standard Time', + 'America/Cayman': 'SA Pacific Standard Time', + 'America/Chicago': 'Central Standard Time', + 'America/Chihuahua': 'Mountain Standard Time (Mexico)', + 'America/Coral_Harbour': 'SA Pacific Standard Time', + 'America/Cordoba': 'Argentina Standard Time', + 'America/Costa_Rica': 'Central America Standard Time', + 'America/Creston': 'US Mountain Standard Time', + 'America/Cuiaba': 'Central Brazilian Standard Time', + 'America/Curacao': 'SA Western Standard Time', + 'America/Danmarkshavn': 'UTC', + 'America/Dawson': 'Pacific Standard Time', + 'America/Dawson_Creek': 'US Mountain Standard Time', + 'America/Denver': 'Mountain Standard Time', + 'America/Detroit': 'Eastern Standard Time', + 'America/Dominica': 'SA Western Standard Time', + 'America/Edmonton': 'Mountain Standard Time', + 'America/Eirunepe': 'SA Pacific Standard Time', + 'America/El_Salvador': 'Central America Standard Time', + 'America/Ensenada': 'Pacific Standard Time (Mexico)', + 'America/Fort_Nelson': 'US Mountain Standard Time', + 'America/Fortaleza': 'SA Eastern Standard Time', + 'America/Glace_Bay': 'Atlantic Standard Time', + 'America/Godthab': 'Greenland Standard Time', + 'America/Goose_Bay': 'Atlantic Standard Time', + 'America/Grand_Turk': 'Turks And Caicos Standard Time', + 'America/Grenada': 'SA Western Standard Time', + 'America/Guadeloupe': 'SA Western Standard Time', + 'America/Guatemala': 'Central America Standard Time', + 'America/Guayaquil': 'SA Pacific Standard Time', + 'America/Guyana': 'SA Western Standard Time', + 'America/Halifax': 'Atlantic Standard Time', + 'America/Havana': 'Cuba Standard Time', + 'America/Hermosillo': 'US Mountain Standard Time', + 'America/Indiana/Knox': 'Central Standard Time', + 'America/Indiana/Marengo': 'US Eastern Standard Time', + 'America/Indiana/Petersburg': 'Eastern Standard Time', + 'America/Indiana/Tell_City': 'Central Standard Time', + 'America/Indiana/Vevay': 'US Eastern Standard Time', + 'America/Indiana/Vincennes': 'Eastern Standard Time', + 'America/Indiana/Winamac': 'Eastern Standard Time', + 'America/Indianapolis': 'US Eastern Standard Time', + 'America/Inuvik': 'Mountain Standard Time', + 'America/Iqaluit': 'Eastern Standard Time', + 'America/Jamaica': 'SA Pacific Standard Time', + 'America/Jujuy': 'Argentina Standard Time', + 'America/Juneau': 'Alaskan Standard Time', + 'America/Kentucky/Monticello': 'Eastern Standard Time', + 'America/Knox_IN': 'Central Standard Time', + 'America/Kralendijk': 'SA Western Standard Time', + 'America/La_Paz': 'SA Western Standard Time', + 'America/Lima': 'SA Pacific Standard Time', + 'America/Los_Angeles': 'Pacific Standard Time', + 'America/Louisville': 'Eastern Standard Time', + 'America/Lower_Princes': 'SA Western Standard Time', + 'America/Maceio': 'SA Eastern Standard Time', + 'America/Managua': 'Central America Standard Time', + 'America/Manaus': 'SA Western Standard Time', + 'America/Marigot': 'SA Western Standard Time', + 'America/Martinique': 'SA Western Standard Time', + 'America/Matamoros': 'Central Standard Time', + 'America/Mazatlan': 'Mountain Standard Time (Mexico)', + 'America/Mendoza': 'Argentina Standard Time', + 'America/Menominee': 'Central Standard Time', + 'America/Merida': 'Central Standard Time (Mexico)', + 'America/Metlakatla': 'Alaskan Standard Time', + 'America/Mexico_City': 'Central Standard Time (Mexico)', + 'America/Miquelon': 'Saint Pierre Standard Time', + 'America/Moncton': 'Atlantic Standard Time', + 'America/Monterrey': 'Central Standard Time (Mexico)', + 'America/Montevideo': 'Montevideo Standard Time', + 'America/Montreal': 'Eastern Standard Time', + 'America/Montserrat': 'SA Western Standard Time', + 'America/Nassau': 'Eastern Standard Time', + 'America/New_York': 'Eastern Standard Time', + 'America/Nipigon': 'Eastern Standard Time', + 'America/Nome': 'Alaskan Standard Time', + 'America/Noronha': 'UTC-02', + 'America/North_Dakota/Beulah': 'Central Standard Time', + 'America/North_Dakota/Center': 'Central Standard Time', + 'America/North_Dakota/New_Salem': 'Central Standard Time', + 'America/Ojinaga': 'Mountain Standard Time', + 'America/Panama': 'SA Pacific Standard Time', + 'America/Pangnirtung': 'Eastern Standard Time', + 'America/Paramaribo': 'SA Eastern Standard Time', + 'America/Phoenix': 'US Mountain Standard Time', + 'America/Port-au-Prince': 'Haiti Standard Time', + 'America/Port_of_Spain': 'SA Western Standard Time', + 'America/Porto_Acre': 'SA Pacific Standard Time', + 'America/Porto_Velho': 'SA Western Standard Time', + 'America/Puerto_Rico': 'SA Western Standard Time', + 'America/Punta_Arenas': 'Magallanes Standard Time', + 'America/Rainy_River': 'Central Standard Time', + 'America/Rankin_Inlet': 'Central Standard Time', + 'America/Recife': 'SA Eastern Standard Time', + 'America/Regina': 'Canada Central Standard Time', + 'America/Resolute': 'Central Standard Time', + 'America/Rio_Branco': 'SA Pacific Standard Time', + 'America/Santa_Isabel': 'Pacific Standard Time (Mexico)', + 'America/Santarem': 'SA Eastern Standard Time', + 'America/Santiago': 'Pacific SA Standard Time', + 'America/Santo_Domingo': 'SA Western Standard Time', + 'America/Sao_Paulo': 'E. South America Standard Time', + 'America/Scoresbysund': 'Azores Standard Time', + 'America/Shiprock': 'Mountain Standard Time', + 'America/Sitka': 'Alaskan Standard Time', + 'America/St_Barthelemy': 'SA Western Standard Time', + 'America/St_Johns': 'Newfoundland Standard Time', + 'America/St_Kitts': 'SA Western Standard Time', + 'America/St_Lucia': 'SA Western Standard Time', + 'America/St_Thomas': 'SA Western Standard Time', + 'America/St_Vincent': 'SA Western Standard Time', + 'America/Swift_Current': 'Canada Central Standard Time', + 'America/Tegucigalpa': 'Central America Standard Time', + 'America/Thule': 'Atlantic Standard Time', + 'America/Thunder_Bay': 'Eastern Standard Time', + 'America/Tijuana': 'Pacific Standard Time (Mexico)', + 'America/Toronto': 'Eastern Standard Time', + 'America/Tortola': 'SA Western Standard Time', + 'America/Vancouver': 'Pacific Standard Time', + 'America/Virgin': 'SA Western Standard Time', + 'America/Whitehorse': 'Pacific Standard Time', + 'America/Winnipeg': 'Central Standard Time', + 'America/Yakutat': 'Alaskan Standard Time', + 'America/Yellowknife': 'Mountain Standard Time', + 'Antarctica/Casey': 'Singapore Standard Time', + 'Antarctica/Davis': 'SE Asia Standard Time', + 'Antarctica/DumontDUrville': 'West Pacific Standard Time', + 'Antarctica/Macquarie': 'Central Pacific Standard Time', + 'Antarctica/Mawson': 'West Asia Standard Time', + 'Antarctica/McMurdo': 'New Zealand Standard Time', + 'Antarctica/Palmer': 'SA Eastern Standard Time', + 'Antarctica/Rothera': 'SA Eastern Standard Time', + 'Antarctica/South_Pole': 'New Zealand Standard Time', + 'Antarctica/Syowa': 'E. Africa Standard Time', + 'Antarctica/Vostok': 'Central Asia Standard Time', + 'Arctic/Longyearbyen': 'W. Europe Standard Time', + 'Asia/Aden': 'Arab Standard Time', + 'Asia/Almaty': 'Central Asia Standard Time', + 'Asia/Amman': 'Jordan Standard Time', + 'Asia/Anadyr': 'Russia Time Zone 11', + 'Asia/Aqtau': 'West Asia Standard Time', + 'Asia/Aqtobe': 'West Asia Standard Time', + 'Asia/Ashgabat': 'West Asia Standard Time', + 'Asia/Ashkhabad': 'West Asia Standard Time', + 'Asia/Atyrau': 'West Asia Standard Time', + 'Asia/Baghdad': 'Arabic Standard Time', + 'Asia/Bahrain': 'Arab Standard Time', + 'Asia/Baku': 'Azerbaijan Standard Time', + 'Asia/Bangkok': 'SE Asia Standard Time', + 'Asia/Barnaul': 'Altai Standard Time', + 'Asia/Beirut': 'Middle East Standard Time', + 'Asia/Bishkek': 'Central Asia Standard Time', + 'Asia/Brunei': 'Singapore Standard Time', + 'Asia/Calcutta': 'India Standard Time', + 'Asia/Chita': 'Transbaikal Standard Time', + 'Asia/Choibalsan': 'Ulaanbaatar Standard Time', + 'Asia/Chongqing': 'China Standard Time', + 'Asia/Chungking': 'China Standard Time', + 'Asia/Colombo': 'Sri Lanka Standard Time', + 'Asia/Dacca': 'Bangladesh Standard Time', + 'Asia/Damascus': 'Syria Standard Time', + 'Asia/Dhaka': 'Bangladesh Standard Time', + 'Asia/Dili': 'Tokyo Standard Time', + 'Asia/Dubai': 'Arabian Standard Time', + 'Asia/Dushanbe': 'West Asia Standard Time', + 'Asia/Famagusta': 'GTB Standard Time', + 'Asia/Gaza': 'West Bank Standard Time', + 'Asia/Harbin': 'China Standard Time', + 'Asia/Hebron': 'West Bank Standard Time', + 'Asia/Hong_Kong': 'China Standard Time', + 'Asia/Hovd': 'W. Mongolia Standard Time', + 'Asia/Irkutsk': 'North Asia East Standard Time', + 'Asia/Jakarta': 'SE Asia Standard Time', + 'Asia/Jayapura': 'Tokyo Standard Time', + 'Asia/Jerusalem': 'Israel Standard Time', + 'Asia/Kabul': 'Afghanistan Standard Time', + 'Asia/Kamchatka': 'Russia Time Zone 11', + 'Asia/Karachi': 'Pakistan Standard Time', + 'Asia/Kashgar': 'Central Asia Standard Time', + 'Asia/Katmandu': 'Nepal Standard Time', + 'Asia/Khandyga': 'Yakutsk Standard Time', + 'Asia/Krasnoyarsk': 'North Asia Standard Time', + 'Asia/Kuala_Lumpur': 'Singapore Standard Time', + 'Asia/Kuching': 'Singapore Standard Time', + 'Asia/Kuwait': 'Arab Standard Time', + 'Asia/Macao': 'China Standard Time', + 'Asia/Macau': 'China Standard Time', + 'Asia/Magadan': 'Magadan Standard Time', + 'Asia/Makassar': 'Singapore Standard Time', + 'Asia/Manila': 'Singapore Standard Time', + 'Asia/Muscat': 'Arabian Standard Time', + 'Asia/Nicosia': 'GTB Standard Time', + 'Asia/Novokuznetsk': 'North Asia Standard Time', + 'Asia/Novosibirsk': 'N. Central Asia Standard Time', + 'Asia/Omsk': 'Omsk Standard Time', + 'Asia/Oral': 'West Asia Standard Time', + 'Asia/Phnom_Penh': 'SE Asia Standard Time', + 'Asia/Pontianak': 'SE Asia Standard Time', + 'Asia/Pyongyang': 'North Korea Standard Time', + 'Asia/Qatar': 'Arab Standard Time', + 'Asia/Qostanay': 'Central Asia Standard Time', + 'Asia/Qyzylorda': 'Qyzylorda Standard Time', + 'Asia/Rangoon': 'Myanmar Standard Time', + 'Asia/Riyadh': 'Arab Standard Time', + 'Asia/Saigon': 'SE Asia Standard Time', + 'Asia/Sakhalin': 'Sakhalin Standard Time', + 'Asia/Samarkand': 'West Asia Standard Time', + 'Asia/Seoul': 'Korea Standard Time', + 'Asia/Shanghai': 'China Standard Time', + 'Asia/Singapore': 'Singapore Standard Time', + 'Asia/Srednekolymsk': 'Russia Time Zone 10', + 'Asia/Taipei': 'Taipei Standard Time', + 'Asia/Tashkent': 'West Asia Standard Time', + 'Asia/Tbilisi': 'Georgian Standard Time', + 'Asia/Tehran': 'Iran Standard Time', + 'Asia/Tel_Aviv': 'Israel Standard Time', + 'Asia/Thimbu': 'Bangladesh Standard Time', + 'Asia/Thimphu': 'Bangladesh Standard Time', + 'Asia/Tokyo': 'Tokyo Standard Time', + 'Asia/Tomsk': 'Tomsk Standard Time', + 'Asia/Ujung_Pandang': 'Singapore Standard Time', + 'Asia/Ulaanbaatar': 'Ulaanbaatar Standard Time', + 'Asia/Ulan_Bator': 'Ulaanbaatar Standard Time', + 'Asia/Urumqi': 'Central Asia Standard Time', + 'Asia/Ust-Nera': 'Vladivostok Standard Time', + 'Asia/Vientiane': 'SE Asia Standard Time', + 'Asia/Vladivostok': 'Vladivostok Standard Time', + 'Asia/Yakutsk': 'Yakutsk Standard Time', + 'Asia/Yekaterinburg': 'Ekaterinburg Standard Time', + 'Asia/Yerevan': 'Caucasus Standard Time', + 'Atlantic/Azores': 'Azores Standard Time', + 'Atlantic/Bermuda': 'Atlantic Standard Time', + 'Atlantic/Canary': 'GMT Standard Time', + 'Atlantic/Cape_Verde': 'Cape Verde Standard Time', + 'Atlantic/Faeroe': 'GMT Standard Time', + 'Atlantic/Jan_Mayen': 'W. Europe Standard Time', + 'Atlantic/Madeira': 'GMT Standard Time', + 'Atlantic/Reykjavik': 'Greenwich Standard Time', + 'Atlantic/South_Georgia': 'UTC-02', + 'Atlantic/St_Helena': 'Greenwich Standard Time', + 'Atlantic/Stanley': 'SA Eastern Standard Time', + 'Australia/ACT': 'AUS Eastern Standard Time', + 'Australia/Adelaide': 'Cen. Australia Standard Time', + 'Australia/Brisbane': 'E. Australia Standard Time', + 'Australia/Broken_Hill': 'Cen. Australia Standard Time', + 'Australia/Canberra': 'AUS Eastern Standard Time', + 'Australia/Currie': 'Tasmania Standard Time', + 'Australia/Darwin': 'AUS Central Standard Time', + 'Australia/Eucla': 'Aus Central W. Standard Time', + 'Australia/Hobart': 'Tasmania Standard Time', + 'Australia/LHI': 'Lord Howe Standard Time', + 'Australia/Lindeman': 'E. Australia Standard Time', + 'Australia/Lord_Howe': 'Lord Howe Standard Time', + 'Australia/Melbourne': 'AUS Eastern Standard Time', + 'Australia/NSW': 'AUS Eastern Standard Time', + 'Australia/North': 'AUS Central Standard Time', + 'Australia/Perth': 'W. Australia Standard Time', + 'Australia/Queensland': 'E. Australia Standard Time', + 'Australia/South': 'Cen. Australia Standard Time', + 'Australia/Sydney': 'AUS Eastern Standard Time', + 'Australia/Tasmania': 'Tasmania Standard Time', + 'Australia/Victoria': 'AUS Eastern Standard Time', + 'Australia/West': 'W. Australia Standard Time', + 'Australia/Yancowinna': 'Cen. Australia Standard Time', + 'Brazil/Acre': 'SA Pacific Standard Time', + 'Brazil/DeNoronha': 'UTC-02', + 'Brazil/East': 'E. South America Standard Time', + 'Brazil/West': 'SA Western Standard Time', + 'CST6CDT': 'Central Standard Time', + 'Canada/Atlantic': 'Atlantic Standard Time', + 'Canada/Central': 'Central Standard Time', + 'Canada/Eastern': 'Eastern Standard Time', + 'Canada/Mountain': 'Mountain Standard Time', + 'Canada/Newfoundland': 'Newfoundland Standard Time', + 'Canada/Pacific': 'Pacific Standard Time', + 'Canada/Saskatchewan': 'Canada Central Standard Time', + 'Canada/Yukon': 'Pacific Standard Time', + 'Chile/Continental': 'Pacific SA Standard Time', + 'Chile/EasterIsland': 'Easter Island Standard Time', + 'Cuba': 'Cuba Standard Time', + 'EST5EDT': 'Eastern Standard Time', + 'Egypt': 'Egypt Standard Time', + 'Eire': 'GMT Standard Time', + 'Etc/GMT': 'UTC', + 'Etc/GMT+1': 'Cape Verde Standard Time', + 'Etc/GMT+10': 'Hawaiian Standard Time', + 'Etc/GMT+11': 'UTC-11', + 'Etc/GMT+12': 'Dateline Standard Time', + 'Etc/GMT+2': 'UTC-02', + 'Etc/GMT+3': 'SA Eastern Standard Time', + 'Etc/GMT+4': 'SA Western Standard Time', + 'Etc/GMT+5': 'SA Pacific Standard Time', + 'Etc/GMT+6': 'Central America Standard Time', + 'Etc/GMT+7': 'US Mountain Standard Time', + 'Etc/GMT+8': 'UTC-08', + 'Etc/GMT+9': 'UTC-09', + 'Etc/GMT-1': 'W. Central Africa Standard Time', + 'Etc/GMT-10': 'West Pacific Standard Time', + 'Etc/GMT-11': 'Central Pacific Standard Time', + 'Etc/GMT-12': 'UTC+12', + 'Etc/GMT-13': 'UTC+13', + 'Etc/GMT-14': 'Line Islands Standard Time', + 'Etc/GMT-2': 'South Africa Standard Time', + 'Etc/GMT-3': 'E. Africa Standard Time', + 'Etc/GMT-4': 'Arabian Standard Time', + 'Etc/GMT-5': 'West Asia Standard Time', + 'Etc/GMT-6': 'Central Asia Standard Time', + 'Etc/GMT-7': 'SE Asia Standard Time', + 'Etc/GMT-8': 'Singapore Standard Time', + 'Etc/GMT-9': 'Tokyo Standard Time', + 'Etc/UCT': 'UTC', + 'Etc/UTC': 'UTC', + 'Europe/Amsterdam': 'W. Europe Standard Time', + 'Europe/Andorra': 'W. Europe Standard Time', + 'Europe/Astrakhan': 'Astrakhan Standard Time', + 'Europe/Athens': 'GTB Standard Time', + 'Europe/Belfast': 'GMT Standard Time', + 'Europe/Belgrade': 'Central Europe Standard Time', + 'Europe/Berlin': 'W. Europe Standard Time', + 'Europe/Bratislava': 'Central Europe Standard Time', + 'Europe/Brussels': 'Romance Standard Time', + 'Europe/Bucharest': 'GTB Standard Time', + 'Europe/Budapest': 'Central Europe Standard Time', + 'Europe/Busingen': 'W. Europe Standard Time', + 'Europe/Chisinau': 'E. Europe Standard Time', + 'Europe/Copenhagen': 'Romance Standard Time', + 'Europe/Dublin': 'GMT Standard Time', + 'Europe/Gibraltar': 'W. Europe Standard Time', + 'Europe/Guernsey': 'GMT Standard Time', + 'Europe/Helsinki': 'FLE Standard Time', + 'Europe/Isle_of_Man': 'GMT Standard Time', + 'Europe/Istanbul': 'Turkey Standard Time', + 'Europe/Jersey': 'GMT Standard Time', + 'Europe/Kaliningrad': 'Kaliningrad Standard Time', + 'Europe/Kiev': 'FLE Standard Time', + 'Europe/Kirov': 'Russian Standard Time', + 'Europe/Lisbon': 'GMT Standard Time', + 'Europe/Ljubljana': 'Central Europe Standard Time', + 'Europe/London': 'GMT Standard Time', + 'Europe/Luxembourg': 'W. Europe Standard Time', + 'Europe/Madrid': 'Romance Standard Time', + 'Europe/Malta': 'W. Europe Standard Time', + 'Europe/Mariehamn': 'FLE Standard Time', + 'Europe/Minsk': 'Belarus Standard Time', + 'Europe/Monaco': 'W. Europe Standard Time', + 'Europe/Moscow': 'Russian Standard Time', + 'Europe/Oslo': 'W. Europe Standard Time', + 'Europe/Paris': 'Romance Standard Time', + 'Europe/Podgorica': 'Central Europe Standard Time', + 'Europe/Prague': 'Central Europe Standard Time', + 'Europe/Riga': 'FLE Standard Time', + 'Europe/Rome': 'W. Europe Standard Time', + 'Europe/Samara': 'Russia Time Zone 3', + 'Europe/San_Marino': 'W. Europe Standard Time', + 'Europe/Sarajevo': 'Central European Standard Time', + 'Europe/Saratov': 'Saratov Standard Time', + 'Europe/Simferopol': 'Russian Standard Time', + 'Europe/Skopje': 'Central European Standard Time', + 'Europe/Sofia': 'FLE Standard Time', + 'Europe/Stockholm': 'W. Europe Standard Time', + 'Europe/Tallinn': 'FLE Standard Time', + 'Europe/Tirane': 'Central Europe Standard Time', + 'Europe/Tiraspol': 'E. Europe Standard Time', + 'Europe/Ulyanovsk': 'Astrakhan Standard Time', + 'Europe/Uzhgorod': 'FLE Standard Time', + 'Europe/Vaduz': 'W. Europe Standard Time', + 'Europe/Vatican': 'W. Europe Standard Time', + 'Europe/Vienna': 'W. Europe Standard Time', + 'Europe/Vilnius': 'FLE Standard Time', + 'Europe/Volgograd': 'Volgograd Standard Time', + 'Europe/Warsaw': 'Central European Standard Time', + 'Europe/Zagreb': 'Central European Standard Time', + 'Europe/Zaporozhye': 'FLE Standard Time', + 'Europe/Zurich': 'W. Europe Standard Time', + 'GB': 'GMT Standard Time', + 'GB-Eire': 'GMT Standard Time', + 'GMT+0': 'UTC', + 'GMT-0': 'UTC', + 'GMT0': 'UTC', + 'Greenwich': 'UTC', + 'Hongkong': 'China Standard Time', + 'Iceland': 'Greenwich Standard Time', + 'Indian/Antananarivo': 'E. Africa Standard Time', + 'Indian/Chagos': 'Central Asia Standard Time', + 'Indian/Christmas': 'SE Asia Standard Time', + 'Indian/Cocos': 'Myanmar Standard Time', + 'Indian/Comoro': 'E. Africa Standard Time', + 'Indian/Kerguelen': 'West Asia Standard Time', + 'Indian/Mahe': 'Mauritius Standard Time', + 'Indian/Maldives': 'West Asia Standard Time', + 'Indian/Mauritius': 'Mauritius Standard Time', + 'Indian/Mayotte': 'E. Africa Standard Time', + 'Indian/Reunion': 'Mauritius Standard Time', + 'Iran': 'Iran Standard Time', + 'Israel': 'Israel Standard Time', + 'Jamaica': 'SA Pacific Standard Time', + 'Japan': 'Tokyo Standard Time', + 'Kwajalein': 'UTC+12', + 'Libya': 'Libya Standard Time', + 'MST7MDT': 'Mountain Standard Time', + 'Mexico/BajaNorte': 'Pacific Standard Time (Mexico)', + 'Mexico/BajaSur': 'Mountain Standard Time (Mexico)', + 'Mexico/General': 'Central Standard Time (Mexico)', + 'NZ': 'New Zealand Standard Time', + 'NZ-CHAT': 'Chatham Islands Standard Time', + 'Navajo': 'Mountain Standard Time', + 'PRC': 'China Standard Time', + 'PST8PDT': 'Pacific Standard Time', + 'Pacific/Apia': 'Samoa Standard Time', + 'Pacific/Auckland': 'New Zealand Standard Time', + 'Pacific/Bougainville': 'Bougainville Standard Time', + 'Pacific/Chatham': 'Chatham Islands Standard Time', + 'Pacific/Easter': 'Easter Island Standard Time', + 'Pacific/Efate': 'Central Pacific Standard Time', + 'Pacific/Enderbury': 'UTC+13', + 'Pacific/Fakaofo': 'UTC+13', + 'Pacific/Fiji': 'Fiji Standard Time', + 'Pacific/Funafuti': 'UTC+12', + 'Pacific/Galapagos': 'Central America Standard Time', + 'Pacific/Gambier': 'UTC-09', + 'Pacific/Guadalcanal': 'Central Pacific Standard Time', + 'Pacific/Guam': 'West Pacific Standard Time', + 'Pacific/Honolulu': 'Hawaiian Standard Time', + 'Pacific/Johnston': 'Hawaiian Standard Time', + 'Pacific/Kiritimati': 'Line Islands Standard Time', + 'Pacific/Kosrae': 'Central Pacific Standard Time', + 'Pacific/Kwajalein': 'UTC+12', + 'Pacific/Majuro': 'UTC+12', + 'Pacific/Marquesas': 'Marquesas Standard Time', + 'Pacific/Midway': 'UTC-11', + 'Pacific/Nauru': 'UTC+12', + 'Pacific/Niue': 'UTC-11', + 'Pacific/Norfolk': 'Norfolk Standard Time', + 'Pacific/Noumea': 'Central Pacific Standard Time', + 'Pacific/Pago_Pago': 'UTC-11', + 'Pacific/Palau': 'Tokyo Standard Time', + 'Pacific/Pitcairn': 'UTC-08', + 'Pacific/Ponape': 'Central Pacific Standard Time', + 'Pacific/Port_Moresby': 'West Pacific Standard Time', + 'Pacific/Rarotonga': 'Hawaiian Standard Time', + 'Pacific/Saipan': 'West Pacific Standard Time', + 'Pacific/Samoa': 'UTC-11', + 'Pacific/Tahiti': 'Hawaiian Standard Time', + 'Pacific/Tarawa': 'UTC+12', + 'Pacific/Tongatapu': 'Tonga Standard Time', + 'Pacific/Truk': 'West Pacific Standard Time', + 'Pacific/Wake': 'UTC+12', + 'Pacific/Wallis': 'UTC+12', + 'Poland': 'Central European Standard Time', + 'Portugal': 'GMT Standard Time', + 'ROC': 'Taipei Standard Time', + 'ROK': 'Korea Standard Time', + 'Singapore': 'Singapore Standard Time', + 'Turkey': 'Turkey Standard Time', + 'UCT': 'UTC', + 'US/Alaska': 'Alaskan Standard Time', + 'US/Aleutian': 'Aleutian Standard Time', + 'US/Arizona': 'US Mountain Standard Time', + 'US/Central': 'Central Standard Time', + 'US/Eastern': 'Eastern Standard Time', + 'US/Hawaii': 'Hawaiian Standard Time', + 'US/Indiana-Starke': 'Central Standard Time', + 'US/Michigan': 'Eastern Standard Time', + 'US/Mountain': 'Mountain Standard Time', + 'US/Pacific': 'Pacific Standard Time', + 'US/Samoa': 'UTC-11', + 'UTC': 'UTC', + 'Universal': 'UTC', + 'W-SU': 'Russian Standard Time', + 'Zulu': 'UTC'} diff --git a/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/AUTHORS.txt b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/AUTHORS.txt new file mode 100644 index 0000000000000000000000000000000000000000..72c87d7d38ae7bf859717c333a5ee8230f6ce624 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/AUTHORS.txt @@ -0,0 +1,562 @@ +A_Rog +Aakanksha Agrawal <11389424+rasponic@users.noreply.github.com> +Abhinav Sagar <40603139+abhinavsagar@users.noreply.github.com> +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Tse +Adam Wentz +admin +Adrien Morison +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Aleks Bunin +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Alexey Popravka +Alli +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrey Bulgakov +Andrés Delfino <34587441+andresdelfino@users.noreply.github.com> +Andrés Delfino +Andy Freeland +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anuj Godase +AQNOUCH Mohammed +AraHaan +Arindam Choudhury +Armin Ronacher +Artem +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avner Cohen +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Darnell +Ben Hoyt +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Brandon L. Reiss +Brandt Bucher +Brett Randall +Brian Cristante <33549821+brcrista@users.noreply.github.com> +Brian Cristante +Brian Rosner +BrownTruck +Bruno Oliveira +Bruno Renié +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +c22 +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Chih-Hsuan Yen +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris McDonough +Chris Wolfe +Christian Heimes +Christian Oudard +Christopher Hunt +Christopher Snyder +Clark Boylan +Clay McClure +Cody +Cody Soyland +Colin Watson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Curtis Doty +cytolentino +Damian Quiroga +Dan Black +Dan Savilonis +Dan Sully +daniel +Daniel Collins +Daniel Hahler +Daniel Holth +Daniel Jost +Daniel Shaulov +Daniele Esposti +Daniele Procida +Danny Hermes +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Bordeynik +David Caro +David Evans +David Linke +David Pursehouse +David Tucker +David Wales +Davidovich +derwolfe +Desetude +Diego Caraballo +DiegoCaraballo +Dmitry Gladkov +Domen Kožar +Donald Stufft +Dongweiming +Douglas Thor +DrFeathers +Dustin Ingram +Dwayne Bailey +Ed Morley <501702+edmorley@users.noreply.github.com> +Ed Morley +Eitan Adler +ekristina +elainechan +Eli Schwartz +Eli Schwartz +Emil Burzo +Emil Styrke +Endoh Takanao +enoch +Erdinc Mutlu +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Ernest W Durbin III +Ernest W. Durbin III +Erwin Janssen +Eugene Vereshchagin +everdimension +Felix Yan +fiber-space +Filip Kokosiński +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gdanielson +Geoffrey Lehée +Geoffrey Sneddon +George Song +Georgi Valkov +Giftlin Rajaiah +gizmoguy1 +gkdoc <40815324+gkdoc@users.noreply.github.com> +Gopinath M <31352222+mgopi1990@users.noreply.github.com> +GOTO Hayato <3532528+gh640@users.noreply.github.com> +gpiks +Guilherme Espada +Guy Rozendorn +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +Herbert Pfennig +Hsiaoming Yang +Hugo +Hugo Lopes Tavares +Hugo van Kemenade +hugovk +Hynek Schlawack +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ilya Baryshev +INADA Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Ivan Pozdeev +Jacob Kim +jakirkham +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James Cleveland +James Cleveland +James Firth +James Polley +Jan Pokorný +Jannis Leidel +jarondl +Jason R. Coombs +Jay Graves +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jelmer Vernooij +jenix21 +Jeremy Stanley +Jeremy Zafran +Jiashuo Li +Jim Garrison +Jivan Amara +John Paton +John-Scott Atlakson +johnthagen +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joost Molenaar +Jorge Niedbalski +Joseph Long +Josh Bronson +Josh Hansen +Josh Schneier +Juanjo Bazán +Julian Berman +Julian Gethmann +Julien Demoor +jwg4 +Jyrki Pulliainen +Kai Chen +Kamal Bin Mustafa +kaustav haldar +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kexuan Sun +Kit Randel +kpinc +Krishna Oza +Kumar McMillan +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurie Opperman +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +Loren Carvalho +Lucas Cimon +Ludovic Gasc +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +László Kiss Kollár +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark Williams +Mark Williams +Markus Hametner +Masaki +Masklinn +Matej Stuchlik +Mathew Jennings +Mathieu Bridon +Matt Good +Matt Maker +Matt Robenolt +matthew +Matthew Einhorn +Matthew Gilliard +Matthew Iversen +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna <44498973+mbaluna@users.noreply.github.com> +mdebi <17590103+mdebi@users.noreply.github.com> +memoselyk +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Williamson +michaelpacer +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Monica Baluna +montefra +Monty Taylor +Nate Coraor +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nikhil Benesch +Nitesh Sharma +Nowell Strite +NtaleGrey +nvdv +Ofekmeister +ofrinevo +Oliver Jeeves +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +OMOTO Kenji +Omry Yadan +Oren Held +Oscar Benjamin +Oz N Tiram +Pachwenko <32424503+Pachwenko@users.noreply.github.com> +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavithra Eswaramoorthy <33131404+QueenCoffee@users.noreply.github.com> +Pawel Jasinski +Pekka Klärck +Peter Lisák +Peter Waller +petr-tik +Phaneendra Chiruvella +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Pratik Mallya +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal <7895pulkit@gmail.com> +Qiangning Hong +Quentin Pradet +R. David Murray +Rafael Caricio +Ralf Schmitt +Razzi Abuissa +rdb +Remi Rampin +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Richard Jones +RobberPhex +Robert Collins +Robert McGibbon +Robert T. McGibbon +robin elisha robinson +Roey Berman +Rohan Jain +Rohan Jain +Rohan Jain +Roman Bogorodskiy +Romuald Brunet +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +Roy Wellington Ⅳ +Ryan Wooden +ryneeverett +Sachi King +Salvatore Rinchiera +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sergey Vasilyev +Seth Woodworth +Shlomi Fish +Shovan Maity +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +Sorin Sbarnea +Stavros Korokithakis +Stefan Scherfke +Stephan Erb +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +stonebig +Stéphane Bidoul (ACSONE) +Stéphane Bidoul +Stéphane Klein +Sumana Harihareswara +Sviatoslav Sydorenko +Sviatoslav Sydorenko +Swat009 +Takayuki SHIMIZUKAWA +tbeswick +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tom Forbes +Tom Freudenheim +Tom V +Tomas Orsava +Tomer Chachamu +Tony Beswick +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +Travis Swicegood +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Philippon +Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> +Vitaly Babiy +Vladimir Rutsky +W. Trevor King +Wil Tan +Wilfred Hughes +William ML Leslie +William T Olson +Wilson Mo +wim glenn +Wolfgang Maier +Xavier Fernandez +Xavier Fernandez +xoviat +xtreak +YAMAMOTO Takashi +Yen Chi Hsuan +Yeray Diaz Diaz +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Zearin +Zearin +Zhiping Deng +Zvezdan Petkovic +Łukasz Langa +Семён Марьясин diff --git a/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..737fec5c5352af3d9a6a47a0670da4bdb52c5725 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2019 The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/METADATA b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..78fb5bfdeef0bf10cb4fb67307c01ad95fe22d12 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/METADATA @@ -0,0 +1,1238 @@ +Metadata-Version: 2.1 +Name: urllib3 +Version: 1.25.8 +Summary: HTTP library with thread-safe connection pooling, file post, and more. +Home-page: https://urllib3.readthedocs.io/ +Author: Andrey Petrov +Author-email: andrey.petrov@shazow.net +License: MIT +Project-URL: Documentation, https://urllib3.readthedocs.io/ +Project-URL: Code, https://github.com/urllib3/urllib3 +Project-URL: Issue tracker, https://github.com/urllib3/urllib3/issues +Keywords: urllib httplib threadsafe filepost http https ssl pooling +Platform: UNKNOWN +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Software Development :: Libraries +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4 +Provides-Extra: brotli +Requires-Dist: brotlipy (>=0.6.0) ; extra == 'brotli' +Provides-Extra: secure +Requires-Dist: certifi ; extra == 'secure' +Requires-Dist: cryptography (>=1.3.4) ; extra == 'secure' +Requires-Dist: idna (>=2.0.0) ; extra == 'secure' +Requires-Dist: pyOpenSSL (>=0.14) ; extra == 'secure' +Requires-Dist: ipaddress ; (python_version == "2.7") and extra == 'secure' +Provides-Extra: socks +Requires-Dist: PySocks (!=1.5.7,<2.0,>=1.5.6) ; extra == 'socks' + +urllib3 +======= + +urllib3 is a powerful, *sanity-friendly* HTTP client for Python. Much of the +Python ecosystem already uses urllib3 and you should too. +urllib3 brings many critical features that are missing from the Python +standard libraries: + +- Thread safety. +- Connection pooling. +- Client-side SSL/TLS verification. +- File uploads with multipart encoding. +- Helpers for retrying requests and dealing with HTTP redirects. +- Support for gzip, deflate, and brotli encoding. +- Proxy support for HTTP and SOCKS. +- 100% test coverage. + +urllib3 is powerful and easy to use:: + + >>> import urllib3 + >>> http = urllib3.PoolManager() + >>> r = http.request('GET', 'http://httpbin.org/robots.txt') + >>> r.status + 200 + >>> r.data + 'User-agent: *\nDisallow: /deny\n' + + +Installing +---------- + +urllib3 can be installed with `pip `_:: + + $ pip install urllib3 + +Alternatively, you can grab the latest source code from `GitHub `_:: + + $ git clone git://github.com/urllib3/urllib3.git + $ python setup.py install + + +Documentation +------------- + +urllib3 has usage and reference documentation at `urllib3.readthedocs.io `_. + + +Contributing +------------ + +urllib3 happily accepts contributions. Please see our +`contributing documentation `_ +for some tips on getting started. + + +Security Disclosures +-------------------- + +To report a security vulnerability, please use the +`Tidelift security contact `_. +Tidelift will coordinate the fix and disclosure with maintainers. + +Maintainers +----------- + +- `@sethmlarson `_ (Seth M. Larson) +- `@pquentin `_ (Quentin Pradet) +- `@theacodes `_ (Thea Flowers) +- `@haikuginger `_ (Jess Shapiro) +- `@lukasa `_ (Cory Benfield) +- `@sigmavirus24 `_ (Ian Stapleton Cordasco) +- `@shazow `_ (Andrey Petrov) + +👋 + + +Sponsorship +----------- + +.. |tideliftlogo| image:: https://nedbatchelder.com/pix/Tidelift_Logos_RGB_Tidelift_Shorthand_On-White_small.png + :width: 75 + :alt: Tidelift + +.. list-table:: + :widths: 10 100 + + * - |tideliftlogo| + - Professional support for urllib3 is available as part of the `Tidelift + Subscription`_. Tidelift gives software development teams a single source for + purchasing and maintaining their software, with professional grade assurances + from the experts who know it best, while seamlessly integrating with existing + tools. + +.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-urllib3?utm_source=pypi-urllib3&utm_medium=referral&utm_campaign=readme + +If your company benefits from this library, please consider `sponsoring its +development `_. + +Sponsors include: + +- Abbott (2018-2019), sponsored `@sethmlarson `_'s work on urllib3. +- Google Cloud Platform (2018-2019), sponsored `@theacodes `_'s work on urllib3. +- Akamai (2017-2018), sponsored `@haikuginger `_'s work on urllib3 +- Hewlett Packard Enterprise (2016-2017), sponsored `@Lukasa’s `_ work on urllib3. + + +Changes +======= + +1.25.8 (2020-01-20) +------------------- + +* Drop support for EOL Python 3.4 (Pull #1774) + +* Optimize _encode_invalid_chars (Pull #1787) + + +1.25.7 (2019-11-11) +------------------- + +* Preserve ``chunked`` parameter on retries (Pull #1715, Pull #1734) + +* Allow unset ``SERVER_SOFTWARE`` in App Engine (Pull #1704, Issue #1470) + +* Fix issue where URL fragment was sent within the request target. (Pull #1732) + +* Fix issue where an empty query section in a URL would fail to parse. (Pull #1732) + +* Remove TLS 1.3 support in SecureTransport due to Apple removing support (Pull #1703) + + +1.25.6 (2019-09-24) +------------------- + +* Fix issue where tilde (``~``) characters were incorrectly + percent-encoded in the path. (Pull #1692) + + +1.25.5 (2019-09-19) +------------------- + +* Add mitigation for BPO-37428 affecting Python <3.7.4 and OpenSSL 1.1.1+ which + caused certificate verification to be enabled when using ``cert_reqs=CERT_NONE``. + (Issue #1682) + + +1.25.4 (2019-09-19) +------------------- + +* Propagate Retry-After header settings to subsequent retries. (Pull #1607) + +* Fix edge case where Retry-After header was still respected even when + explicitly opted out of. (Pull #1607) + +* Remove dependency on ``rfc3986`` for URL parsing. + +* Fix issue where URLs containing invalid characters within ``Url.auth`` would + raise an exception instead of percent-encoding those characters. + +* Add support for ``HTTPResponse.auto_close = False`` which makes HTTP responses + work well with BufferedReaders and other ``io`` module features. (Pull #1652) + +* Percent-encode invalid characters in URL for ``HTTPConnectionPool.request()`` (Pull #1673) + + +1.25.3 (2019-05-23) +------------------- + +* Change ``HTTPSConnection`` to load system CA certificates + when ``ca_certs``, ``ca_cert_dir``, and ``ssl_context`` are + unspecified. (Pull #1608, Issue #1603) + +* Upgrade bundled rfc3986 to v1.3.2. (Pull #1609, Issue #1605) + + +1.25.2 (2019-04-28) +------------------- + +* Change ``is_ipaddress`` to not detect IPvFuture addresses. (Pull #1583) + +* Change ``parse_url`` to percent-encode invalid characters within the + path, query, and target components. (Pull #1586) + + +1.25.1 (2019-04-24) +------------------- + +* Add support for Google's ``Brotli`` package. (Pull #1572, Pull #1579) + +* Upgrade bundled rfc3986 to v1.3.1 (Pull #1578) + + +1.25 (2019-04-22) +----------------- + +* Require and validate certificates by default when using HTTPS (Pull #1507) + +* Upgraded ``urllib3.utils.parse_url()`` to be RFC 3986 compliant. (Pull #1487) + +* Added support for ``key_password`` for ``HTTPSConnectionPool`` to use + encrypted ``key_file`` without creating your own ``SSLContext`` object. (Pull #1489) + +* Add TLSv1.3 support to CPython, pyOpenSSL, and SecureTransport ``SSLContext`` + implementations. (Pull #1496) + +* Switched the default multipart header encoder from RFC 2231 to HTML 5 working draft. (Issue #303, PR #1492) + +* Fixed issue where OpenSSL would block if an encrypted client private key was + given and no password was given. Instead an ``SSLError`` is raised. (Pull #1489) + +* Added support for Brotli content encoding. It is enabled automatically if + ``brotlipy`` package is installed which can be requested with + ``urllib3[brotli]`` extra. (Pull #1532) + +* Drop ciphers using DSS key exchange from default TLS cipher suites. + Improve default ciphers when using SecureTransport. (Pull #1496) + +* Implemented a more efficient ``HTTPResponse.__iter__()`` method. (Issue #1483) + +1.24.3 (2019-05-01) +------------------- + +* Apply fix for CVE-2019-9740. (Pull #1591) + +1.24.2 (2019-04-17) +------------------- + +* Don't load system certificates by default when any other ``ca_certs``, ``ca_certs_dir`` or + ``ssl_context`` parameters are specified. + +* Remove Authorization header regardless of case when redirecting to cross-site. (Issue #1510) + +* Add support for IPv6 addresses in subjectAltName section of certificates. (Issue #1269) + + +1.24.1 (2018-11-02) +------------------- + +* Remove quadratic behavior within ``GzipDecoder.decompress()`` (Issue #1467) + +* Restored functionality of ``ciphers`` parameter for ``create_urllib3_context()``. (Issue #1462) + + +1.24 (2018-10-16) +----------------- + +* Allow key_server_hostname to be specified when initializing a PoolManager to allow custom SNI to be overridden. (Pull #1449) + +* Test against Python 3.7 on AppVeyor. (Pull #1453) + +* Early-out ipv6 checks when running on App Engine. (Pull #1450) + +* Change ambiguous description of backoff_factor (Pull #1436) + +* Add ability to handle multiple Content-Encodings (Issue #1441 and Pull #1442) + +* Skip DNS names that can't be idna-decoded when using pyOpenSSL (Issue #1405). + +* Add a server_hostname parameter to HTTPSConnection which allows for + overriding the SNI hostname sent in the handshake. (Pull #1397) + +* Drop support for EOL Python 2.6 (Pull #1429 and Pull #1430) + +* Fixed bug where responses with header Content-Type: message/* erroneously + raised HeaderParsingError, resulting in a warning being logged. (Pull #1439) + +* Move urllib3 to src/urllib3 (Pull #1409) + + +1.23 (2018-06-04) +----------------- + +* Allow providing a list of headers to strip from requests when redirecting + to a different host. Defaults to the ``Authorization`` header. Different + headers can be set via ``Retry.remove_headers_on_redirect``. (Issue #1316) + +* Fix ``util.selectors._fileobj_to_fd`` to accept ``long`` (Issue #1247). + +* Dropped Python 3.3 support. (Pull #1242) + +* Put the connection back in the pool when calling stream() or read_chunked() on + a chunked HEAD response. (Issue #1234) + +* Fixed pyOpenSSL-specific ssl client authentication issue when clients + attempted to auth via certificate + chain (Issue #1060) + +* Add the port to the connectionpool connect print (Pull #1251) + +* Don't use the ``uuid`` module to create multipart data boundaries. (Pull #1380) + +* ``read_chunked()`` on a closed response returns no chunks. (Issue #1088) + +* Add Python 2.6 support to ``contrib.securetransport`` (Pull #1359) + +* Added support for auth info in url for SOCKS proxy (Pull #1363) + + +1.22 (2017-07-20) +----------------- + +* Fixed missing brackets in ``HTTP CONNECT`` when connecting to IPv6 address via + IPv6 proxy. (Issue #1222) + +* Made the connection pool retry on ``SSLError``. The original ``SSLError`` + is available on ``MaxRetryError.reason``. (Issue #1112) + +* Drain and release connection before recursing on retry/redirect. Fixes + deadlocks with a blocking connectionpool. (Issue #1167) + +* Fixed compatibility for cookiejar. (Issue #1229) + +* pyopenssl: Use vendored version of ``six``. (Issue #1231) + + +1.21.1 (2017-05-02) +------------------- + +* Fixed SecureTransport issue that would cause long delays in response body + delivery. (Pull #1154) + +* Fixed regression in 1.21 that threw exceptions when users passed the + ``socket_options`` flag to the ``PoolManager``. (Issue #1165) + +* Fixed regression in 1.21 that threw exceptions when users passed the + ``assert_hostname`` or ``assert_fingerprint`` flag to the ``PoolManager``. + (Pull #1157) + + +1.21 (2017-04-25) +----------------- + +* Improved performance of certain selector system calls on Python 3.5 and + later. (Pull #1095) + +* Resolved issue where the PyOpenSSL backend would not wrap SysCallError + exceptions appropriately when sending data. (Pull #1125) + +* Selectors now detects a monkey-patched select module after import for modules + that patch the select module like eventlet, greenlet. (Pull #1128) + +* Reduced memory consumption when streaming zlib-compressed responses + (as opposed to raw deflate streams). (Pull #1129) + +* Connection pools now use the entire request context when constructing the + pool key. (Pull #1016) + +* ``PoolManager.connection_from_*`` methods now accept a new keyword argument, + ``pool_kwargs``, which are merged with the existing ``connection_pool_kw``. + (Pull #1016) + +* Add retry counter for ``status_forcelist``. (Issue #1147) + +* Added ``contrib`` module for using SecureTransport on macOS: + ``urllib3.contrib.securetransport``. (Pull #1122) + +* urllib3 now only normalizes the case of ``http://`` and ``https://`` schemes: + for schemes it does not recognise, it assumes they are case-sensitive and + leaves them unchanged. + (Issue #1080) + + +1.20 (2017-01-19) +----------------- + +* Added support for waiting for I/O using selectors other than select, + improving urllib3's behaviour with large numbers of concurrent connections. + (Pull #1001) + +* Updated the date for the system clock check. (Issue #1005) + +* ConnectionPools now correctly consider hostnames to be case-insensitive. + (Issue #1032) + +* Outdated versions of PyOpenSSL now cause the PyOpenSSL contrib module + to fail when it is injected, rather than at first use. (Pull #1063) + +* Outdated versions of cryptography now cause the PyOpenSSL contrib module + to fail when it is injected, rather than at first use. (Issue #1044) + +* Automatically attempt to rewind a file-like body object when a request is + retried or redirected. (Pull #1039) + +* Fix some bugs that occur when modules incautiously patch the queue module. + (Pull #1061) + +* Prevent retries from occurring on read timeouts for which the request method + was not in the method whitelist. (Issue #1059) + +* Changed the PyOpenSSL contrib module to lazily load idna to avoid + unnecessarily bloating the memory of programs that don't need it. (Pull + #1076) + +* Add support for IPv6 literals with zone identifiers. (Pull #1013) + +* Added support for socks5h:// and socks4a:// schemes when working with SOCKS + proxies, and controlled remote DNS appropriately. (Issue #1035) + + +1.19.1 (2016-11-16) +------------------- + +* Fixed AppEngine import that didn't function on Python 3.5. (Pull #1025) + + +1.19 (2016-11-03) +----------------- + +* urllib3 now respects Retry-After headers on 413, 429, and 503 responses when + using the default retry logic. (Pull #955) + +* Remove markers from setup.py to assist ancient setuptools versions. (Issue + #986) + +* Disallow superscripts and other integerish things in URL ports. (Issue #989) + +* Allow urllib3's HTTPResponse.stream() method to continue to work with + non-httplib underlying FPs. (Pull #990) + +* Empty filenames in multipart headers are now emitted as such, rather than + being suppressed. (Issue #1015) + +* Prefer user-supplied Host headers on chunked uploads. (Issue #1009) + + +1.18.1 (2016-10-27) +------------------- + +* CVE-2016-9015. Users who are using urllib3 version 1.17 or 1.18 along with + PyOpenSSL injection and OpenSSL 1.1.0 *must* upgrade to this version. This + release fixes a vulnerability whereby urllib3 in the above configuration + would silently fail to validate TLS certificates due to erroneously setting + invalid flags in OpenSSL's ``SSL_CTX_set_verify`` function. These erroneous + flags do not cause a problem in OpenSSL versions before 1.1.0, which + interprets the presence of any flag as requesting certificate validation. + + There is no PR for this patch, as it was prepared for simultaneous disclosure + and release. The master branch received the same fix in PR #1010. + + +1.18 (2016-09-26) +----------------- + +* Fixed incorrect message for IncompleteRead exception. (PR #973) + +* Accept ``iPAddress`` subject alternative name fields in TLS certificates. + (Issue #258) + +* Fixed consistency of ``HTTPResponse.closed`` between Python 2 and 3. + (Issue #977) + +* Fixed handling of wildcard certificates when using PyOpenSSL. (Issue #979) + + +1.17 (2016-09-06) +----------------- + +* Accept ``SSLContext`` objects for use in SSL/TLS negotiation. (Issue #835) + +* ConnectionPool debug log now includes scheme, host, and port. (Issue #897) + +* Substantially refactored documentation. (Issue #887) + +* Used URLFetch default timeout on AppEngine, rather than hardcoding our own. + (Issue #858) + +* Normalize the scheme and host in the URL parser (Issue #833) + +* ``HTTPResponse`` contains the last ``Retry`` object, which now also + contains retries history. (Issue #848) + +* Timeout can no longer be set as boolean, and must be greater than zero. + (PR #924) + +* Removed pyasn1 and ndg-httpsclient from dependencies used for PyOpenSSL. We + now use cryptography and idna, both of which are already dependencies of + PyOpenSSL. (PR #930) + +* Fixed infinite loop in ``stream`` when amt=None. (Issue #928) + +* Try to use the operating system's certificates when we are using an + ``SSLContext``. (PR #941) + +* Updated cipher suite list to allow ChaCha20+Poly1305. AES-GCM is preferred to + ChaCha20, but ChaCha20 is then preferred to everything else. (PR #947) + +* Updated cipher suite list to remove 3DES-based cipher suites. (PR #958) + +* Removed the cipher suite fallback to allow HIGH ciphers. (PR #958) + +* Implemented ``length_remaining`` to determine remaining content + to be read. (PR #949) + +* Implemented ``enforce_content_length`` to enable exceptions when + incomplete data chunks are received. (PR #949) + +* Dropped connection start, dropped connection reset, redirect, forced retry, + and new HTTPS connection log levels to DEBUG, from INFO. (PR #967) + + +1.16 (2016-06-11) +----------------- + +* Disable IPv6 DNS when IPv6 connections are not possible. (Issue #840) + +* Provide ``key_fn_by_scheme`` pool keying mechanism that can be + overridden. (Issue #830) + +* Normalize scheme and host to lowercase for pool keys, and include + ``source_address``. (Issue #830) + +* Cleaner exception chain in Python 3 for ``_make_request``. + (Issue #861) + +* Fixed installing ``urllib3[socks]`` extra. (Issue #864) + +* Fixed signature of ``ConnectionPool.close`` so it can actually safely be + called by subclasses. (Issue #873) + +* Retain ``release_conn`` state across retries. (Issues #651, #866) + +* Add customizable ``HTTPConnectionPool.ResponseCls``, which defaults to + ``HTTPResponse`` but can be replaced with a subclass. (Issue #879) + + +1.15.1 (2016-04-11) +------------------- + +* Fix packaging to include backports module. (Issue #841) + + +1.15 (2016-04-06) +----------------- + +* Added Retry(raise_on_status=False). (Issue #720) + +* Always use setuptools, no more distutils fallback. (Issue #785) + +* Dropped support for Python 3.2. (Issue #786) + +* Chunked transfer encoding when requesting with ``chunked=True``. + (Issue #790) + +* Fixed regression with IPv6 port parsing. (Issue #801) + +* Append SNIMissingWarning messages to allow users to specify it in + the PYTHONWARNINGS environment variable. (Issue #816) + +* Handle unicode headers in Py2. (Issue #818) + +* Log certificate when there is a hostname mismatch. (Issue #820) + +* Preserve order of request/response headers. (Issue #821) + + +1.14 (2015-12-29) +----------------- + +* contrib: SOCKS proxy support! (Issue #762) + +* Fixed AppEngine handling of transfer-encoding header and bug + in Timeout defaults checking. (Issue #763) + + +1.13.1 (2015-12-18) +------------------- + +* Fixed regression in IPv6 + SSL for match_hostname. (Issue #761) + + +1.13 (2015-12-14) +----------------- + +* Fixed ``pip install urllib3[secure]`` on modern pip. (Issue #706) + +* pyopenssl: Fixed SSL3_WRITE_PENDING error. (Issue #717) + +* pyopenssl: Support for TLSv1.1 and TLSv1.2. (Issue #696) + +* Close connections more defensively on exception. (Issue #734) + +* Adjusted ``read_chunked`` to handle gzipped, chunk-encoded bodies without + repeatedly flushing the decoder, to function better on Jython. (Issue #743) + +* Accept ``ca_cert_dir`` for SSL-related PoolManager configuration. (Issue #758) + + +1.12 (2015-09-03) +----------------- + +* Rely on ``six`` for importing ``httplib`` to work around + conflicts with other Python 3 shims. (Issue #688) + +* Add support for directories of certificate authorities, as supported by + OpenSSL. (Issue #701) + +* New exception: ``NewConnectionError``, raised when we fail to establish + a new connection, usually ``ECONNREFUSED`` socket error. + + +1.11 (2015-07-21) +----------------- + +* When ``ca_certs`` is given, ``cert_reqs`` defaults to + ``'CERT_REQUIRED'``. (Issue #650) + +* ``pip install urllib3[secure]`` will install Certifi and + PyOpenSSL as dependencies. (Issue #678) + +* Made ``HTTPHeaderDict`` usable as a ``headers`` input value + (Issues #632, #679) + +* Added `urllib3.contrib.appengine `_ + which has an ``AppEngineManager`` for using ``URLFetch`` in a + Google AppEngine environment. (Issue #664) + +* Dev: Added test suite for AppEngine. (Issue #631) + +* Fix performance regression when using PyOpenSSL. (Issue #626) + +* Passing incorrect scheme (e.g. ``foo://``) will raise + ``ValueError`` instead of ``AssertionError`` (backwards + compatible for now, but please migrate). (Issue #640) + +* Fix pools not getting replenished when an error occurs during a + request using ``release_conn=False``. (Issue #644) + +* Fix pool-default headers not applying for url-encoded requests + like GET. (Issue #657) + +* log.warning in Python 3 when headers are skipped due to parsing + errors. (Issue #642) + +* Close and discard connections if an error occurs during read. + (Issue #660) + +* Fix host parsing for IPv6 proxies. (Issue #668) + +* Separate warning type SubjectAltNameWarning, now issued once + per host. (Issue #671) + +* Fix ``httplib.IncompleteRead`` not getting converted to + ``ProtocolError`` when using ``HTTPResponse.stream()`` + (Issue #674) + +1.10.4 (2015-05-03) +------------------- + +* Migrate tests to Tornado 4. (Issue #594) + +* Append default warning configuration rather than overwrite. + (Issue #603) + +* Fix streaming decoding regression. (Issue #595) + +* Fix chunked requests losing state across keep-alive connections. + (Issue #599) + +* Fix hanging when chunked HEAD response has no body. (Issue #605) + + +1.10.3 (2015-04-21) +------------------- + +* Emit ``InsecurePlatformWarning`` when SSLContext object is missing. + (Issue #558) + +* Fix regression of duplicate header keys being discarded. + (Issue #563) + +* ``Response.stream()`` returns a generator for chunked responses. + (Issue #560) + +* Set upper-bound timeout when waiting for a socket in PyOpenSSL. + (Issue #585) + +* Work on platforms without `ssl` module for plain HTTP requests. + (Issue #587) + +* Stop relying on the stdlib's default cipher list. (Issue #588) + + +1.10.2 (2015-02-25) +------------------- + +* Fix file descriptor leakage on retries. (Issue #548) + +* Removed RC4 from default cipher list. (Issue #551) + +* Header performance improvements. (Issue #544) + +* Fix PoolManager not obeying redirect retry settings. (Issue #553) + + +1.10.1 (2015-02-10) +------------------- + +* Pools can be used as context managers. (Issue #545) + +* Don't re-use connections which experienced an SSLError. (Issue #529) + +* Don't fail when gzip decoding an empty stream. (Issue #535) + +* Add sha256 support for fingerprint verification. (Issue #540) + +* Fixed handling of header values containing commas. (Issue #533) + + +1.10 (2014-12-14) +----------------- + +* Disabled SSLv3. (Issue #473) + +* Add ``Url.url`` property to return the composed url string. (Issue #394) + +* Fixed PyOpenSSL + gevent ``WantWriteError``. (Issue #412) + +* ``MaxRetryError.reason`` will always be an exception, not string. + (Issue #481) + +* Fixed SSL-related timeouts not being detected as timeouts. (Issue #492) + +* Py3: Use ``ssl.create_default_context()`` when available. (Issue #473) + +* Emit ``InsecureRequestWarning`` for *every* insecure HTTPS request. + (Issue #496) + +* Emit ``SecurityWarning`` when certificate has no ``subjectAltName``. + (Issue #499) + +* Close and discard sockets which experienced SSL-related errors. + (Issue #501) + +* Handle ``body`` param in ``.request(...)``. (Issue #513) + +* Respect timeout with HTTPS proxy. (Issue #505) + +* PyOpenSSL: Handle ZeroReturnError exception. (Issue #520) + + +1.9.1 (2014-09-13) +------------------ + +* Apply socket arguments before binding. (Issue #427) + +* More careful checks if fp-like object is closed. (Issue #435) + +* Fixed packaging issues of some development-related files not + getting included. (Issue #440) + +* Allow performing *only* fingerprint verification. (Issue #444) + +* Emit ``SecurityWarning`` if system clock is waaay off. (Issue #445) + +* Fixed PyOpenSSL compatibility with PyPy. (Issue #450) + +* Fixed ``BrokenPipeError`` and ``ConnectionError`` handling in Py3. + (Issue #443) + + + +1.9 (2014-07-04) +---------------- + +* Shuffled around development-related files. If you're maintaining a distro + package of urllib3, you may need to tweak things. (Issue #415) + +* Unverified HTTPS requests will trigger a warning on the first request. See + our new `security documentation + `_ for details. + (Issue #426) + +* New retry logic and ``urllib3.util.retry.Retry`` configuration object. + (Issue #326) + +* All raised exceptions should now wrapped in a + ``urllib3.exceptions.HTTPException``-extending exception. (Issue #326) + +* All errors during a retry-enabled request should be wrapped in + ``urllib3.exceptions.MaxRetryError``, including timeout-related exceptions + which were previously exempt. Underlying error is accessible from the + ``.reason`` property. (Issue #326) + +* ``urllib3.exceptions.ConnectionError`` renamed to + ``urllib3.exceptions.ProtocolError``. (Issue #326) + +* Errors during response read (such as IncompleteRead) are now wrapped in + ``urllib3.exceptions.ProtocolError``. (Issue #418) + +* Requesting an empty host will raise ``urllib3.exceptions.LocationValueError``. + (Issue #417) + +* Catch read timeouts over SSL connections as + ``urllib3.exceptions.ReadTimeoutError``. (Issue #419) + +* Apply socket arguments before connecting. (Issue #427) + + +1.8.3 (2014-06-23) +------------------ + +* Fix TLS verification when using a proxy in Python 3.4.1. (Issue #385) + +* Add ``disable_cache`` option to ``urllib3.util.make_headers``. (Issue #393) + +* Wrap ``socket.timeout`` exception with + ``urllib3.exceptions.ReadTimeoutError``. (Issue #399) + +* Fixed proxy-related bug where connections were being reused incorrectly. + (Issues #366, #369) + +* Added ``socket_options`` keyword parameter which allows to define + ``setsockopt`` configuration of new sockets. (Issue #397) + +* Removed ``HTTPConnection.tcp_nodelay`` in favor of + ``HTTPConnection.default_socket_options``. (Issue #397) + +* Fixed ``TypeError`` bug in Python 2.6.4. (Issue #411) + + +1.8.2 (2014-04-17) +------------------ + +* Fix ``urllib3.util`` not being included in the package. + + +1.8.1 (2014-04-17) +------------------ + +* Fix AppEngine bug of HTTPS requests going out as HTTP. (Issue #356) + +* Don't install ``dummyserver`` into ``site-packages`` as it's only needed + for the test suite. (Issue #362) + +* Added support for specifying ``source_address``. (Issue #352) + + +1.8 (2014-03-04) +---------------- + +* Improved url parsing in ``urllib3.util.parse_url`` (properly parse '@' in + username, and blank ports like 'hostname:'). + +* New ``urllib3.connection`` module which contains all the HTTPConnection + objects. + +* Several ``urllib3.util.Timeout``-related fixes. Also changed constructor + signature to a more sensible order. [Backwards incompatible] + (Issues #252, #262, #263) + +* Use ``backports.ssl_match_hostname`` if it's installed. (Issue #274) + +* Added ``.tell()`` method to ``urllib3.response.HTTPResponse`` which + returns the number of bytes read so far. (Issue #277) + +* Support for platforms without threading. (Issue #289) + +* Expand default-port comparison in ``HTTPConnectionPool.is_same_host`` + to allow a pool with no specified port to be considered equal to to an + HTTP/HTTPS url with port 80/443 explicitly provided. (Issue #305) + +* Improved default SSL/TLS settings to avoid vulnerabilities. + (Issue #309) + +* Fixed ``urllib3.poolmanager.ProxyManager`` not retrying on connect errors. + (Issue #310) + +* Disable Nagle's Algorithm on the socket for non-proxies. A subset of requests + will send the entire HTTP request ~200 milliseconds faster; however, some of + the resulting TCP packets will be smaller. (Issue #254) + +* Increased maximum number of SubjectAltNames in ``urllib3.contrib.pyopenssl`` + from the default 64 to 1024 in a single certificate. (Issue #318) + +* Headers are now passed and stored as a custom + ``urllib3.collections_.HTTPHeaderDict`` object rather than a plain ``dict``. + (Issue #329, #333) + +* Headers no longer lose their case on Python 3. (Issue #236) + +* ``urllib3.contrib.pyopenssl`` now uses the operating system's default CA + certificates on inject. (Issue #332) + +* Requests with ``retries=False`` will immediately raise any exceptions without + wrapping them in ``MaxRetryError``. (Issue #348) + +* Fixed open socket leak with SSL-related failures. (Issue #344, #348) + + +1.7.1 (2013-09-25) +------------------ + +* Added granular timeout support with new ``urllib3.util.Timeout`` class. + (Issue #231) + +* Fixed Python 3.4 support. (Issue #238) + + +1.7 (2013-08-14) +---------------- + +* More exceptions are now pickle-able, with tests. (Issue #174) + +* Fixed redirecting with relative URLs in Location header. (Issue #178) + +* Support for relative urls in ``Location: ...`` header. (Issue #179) + +* ``urllib3.response.HTTPResponse`` now inherits from ``io.IOBase`` for bonus + file-like functionality. (Issue #187) + +* Passing ``assert_hostname=False`` when creating a HTTPSConnectionPool will + skip hostname verification for SSL connections. (Issue #194) + +* New method ``urllib3.response.HTTPResponse.stream(...)`` which acts as a + generator wrapped around ``.read(...)``. (Issue #198) + +* IPv6 url parsing enforces brackets around the hostname. (Issue #199) + +* Fixed thread race condition in + ``urllib3.poolmanager.PoolManager.connection_from_host(...)`` (Issue #204) + +* ``ProxyManager`` requests now include non-default port in ``Host: ...`` + header. (Issue #217) + +* Added HTTPS proxy support in ``ProxyManager``. (Issue #170 #139) + +* New ``RequestField`` object can be passed to the ``fields=...`` param which + can specify headers. (Issue #220) + +* Raise ``urllib3.exceptions.ProxyError`` when connecting to proxy fails. + (Issue #221) + +* Use international headers when posting file names. (Issue #119) + +* Improved IPv6 support. (Issue #203) + + +1.6 (2013-04-25) +---------------- + +* Contrib: Optional SNI support for Py2 using PyOpenSSL. (Issue #156) + +* ``ProxyManager`` automatically adds ``Host: ...`` header if not given. + +* Improved SSL-related code. ``cert_req`` now optionally takes a string like + "REQUIRED" or "NONE". Same with ``ssl_version`` takes strings like "SSLv23" + The string values reflect the suffix of the respective constant variable. + (Issue #130) + +* Vendored ``socksipy`` now based on Anorov's fork which handles unexpectedly + closed proxy connections and larger read buffers. (Issue #135) + +* Ensure the connection is closed if no data is received, fixes connection leak + on some platforms. (Issue #133) + +* Added SNI support for SSL/TLS connections on Py32+. (Issue #89) + +* Tests fixed to be compatible with Py26 again. (Issue #125) + +* Added ability to choose SSL version by passing an ``ssl.PROTOCOL_*`` constant + to the ``ssl_version`` parameter of ``HTTPSConnectionPool``. (Issue #109) + +* Allow an explicit content type to be specified when encoding file fields. + (Issue #126) + +* Exceptions are now pickleable, with tests. (Issue #101) + +* Fixed default headers not getting passed in some cases. (Issue #99) + +* Treat "content-encoding" header value as case-insensitive, per RFC 2616 + Section 3.5. (Issue #110) + +* "Connection Refused" SocketErrors will get retried rather than raised. + (Issue #92) + +* Updated vendored ``six``, no longer overrides the global ``six`` module + namespace. (Issue #113) + +* ``urllib3.exceptions.MaxRetryError`` contains a ``reason`` property holding + the exception that prompted the final retry. If ``reason is None`` then it + was due to a redirect. (Issue #92, #114) + +* Fixed ``PoolManager.urlopen()`` from not redirecting more than once. + (Issue #149) + +* Don't assume ``Content-Type: text/plain`` for multi-part encoding parameters + that are not files. (Issue #111) + +* Pass `strict` param down to ``httplib.HTTPConnection``. (Issue #122) + +* Added mechanism to verify SSL certificates by fingerprint (md5, sha1) or + against an arbitrary hostname (when connecting by IP or for misconfigured + servers). (Issue #140) + +* Streaming decompression support. (Issue #159) + + +1.5 (2012-08-02) +---------------- + +* Added ``urllib3.add_stderr_logger()`` for quickly enabling STDERR debug + logging in urllib3. + +* Native full URL parsing (including auth, path, query, fragment) available in + ``urllib3.util.parse_url(url)``. + +* Built-in redirect will switch method to 'GET' if status code is 303. + (Issue #11) + +* ``urllib3.PoolManager`` strips the scheme and host before sending the request + uri. (Issue #8) + +* New ``urllib3.exceptions.DecodeError`` exception for when automatic decoding, + based on the Content-Type header, fails. + +* Fixed bug with pool depletion and leaking connections (Issue #76). Added + explicit connection closing on pool eviction. Added + ``urllib3.PoolManager.clear()``. + +* 99% -> 100% unit test coverage. + + +1.4 (2012-06-16) +---------------- + +* Minor AppEngine-related fixes. + +* Switched from ``mimetools.choose_boundary`` to ``uuid.uuid4()``. + +* Improved url parsing. (Issue #73) + +* IPv6 url support. (Issue #72) + + +1.3 (2012-03-25) +---------------- + +* Removed pre-1.0 deprecated API. + +* Refactored helpers into a ``urllib3.util`` submodule. + +* Fixed multipart encoding to support list-of-tuples for keys with multiple + values. (Issue #48) + +* Fixed multiple Set-Cookie headers in response not getting merged properly in + Python 3. (Issue #53) + +* AppEngine support with Py27. (Issue #61) + +* Minor ``encode_multipart_formdata`` fixes related to Python 3 strings vs + bytes. + + +1.2.2 (2012-02-06) +------------------ + +* Fixed packaging bug of not shipping ``test-requirements.txt``. (Issue #47) + + +1.2.1 (2012-02-05) +------------------ + +* Fixed another bug related to when ``ssl`` module is not available. (Issue #41) + +* Location parsing errors now raise ``urllib3.exceptions.LocationParseError`` + which inherits from ``ValueError``. + + +1.2 (2012-01-29) +---------------- + +* Added Python 3 support (tested on 3.2.2) + +* Dropped Python 2.5 support (tested on 2.6.7, 2.7.2) + +* Use ``select.poll`` instead of ``select.select`` for platforms that support + it. + +* Use ``Queue.LifoQueue`` instead of ``Queue.Queue`` for more aggressive + connection reusing. Configurable by overriding ``ConnectionPool.QueueCls``. + +* Fixed ``ImportError`` during install when ``ssl`` module is not available. + (Issue #41) + +* Fixed ``PoolManager`` redirects between schemes (such as HTTP -> HTTPS) not + completing properly. (Issue #28, uncovered by Issue #10 in v1.1) + +* Ported ``dummyserver`` to use ``tornado`` instead of ``webob`` + + ``eventlet``. Removed extraneous unsupported dummyserver testing backends. + Added socket-level tests. + +* More tests. Achievement Unlocked: 99% Coverage. + + +1.1 (2012-01-07) +---------------- + +* Refactored ``dummyserver`` to its own root namespace module (used for + testing). + +* Added hostname verification for ``VerifiedHTTPSConnection`` by vendoring in + Py32's ``ssl_match_hostname``. (Issue #25) + +* Fixed cross-host HTTP redirects when using ``PoolManager``. (Issue #10) + +* Fixed ``decode_content`` being ignored when set through ``urlopen``. (Issue + #27) + +* Fixed timeout-related bugs. (Issues #17, #23) + + +1.0.2 (2011-11-04) +------------------ + +* Fixed typo in ``VerifiedHTTPSConnection`` which would only present as a bug if + you're using the object manually. (Thanks pyos) + +* Made RecentlyUsedContainer (and consequently PoolManager) more thread-safe by + wrapping the access log in a mutex. (Thanks @christer) + +* Made RecentlyUsedContainer more dict-like (corrected ``__delitem__`` and + ``__getitem__`` behaviour), with tests. Shouldn't affect core urllib3 code. + + +1.0.1 (2011-10-10) +------------------ + +* Fixed a bug where the same connection would get returned into the pool twice, + causing extraneous "HttpConnectionPool is full" log warnings. + + +1.0 (2011-10-08) +---------------- + +* Added ``PoolManager`` with LRU expiration of connections (tested and + documented). +* Added ``ProxyManager`` (needs tests, docs, and confirmation that it works + with HTTPS proxies). +* Added optional partial-read support for responses when + ``preload_content=False``. You can now make requests and just read the headers + without loading the content. +* Made response decoding optional (default on, same as before). +* Added optional explicit boundary string for ``encode_multipart_formdata``. +* Convenience request methods are now inherited from ``RequestMethods``. Old + helpers like ``get_url`` and ``post_url`` should be abandoned in favour of + the new ``request(method, url, ...)``. +* Refactored code to be even more decoupled, reusable, and extendable. +* License header added to ``.py`` files. +* Embiggened the documentation: Lots of Sphinx-friendly docstrings in the code + and docs in ``docs/`` and on https://urllib3.readthedocs.io/. +* Embettered all the things! +* Started writing this file. + + +0.4.1 (2011-07-17) +------------------ + +* Minor bug fixes, code cleanup. + + +0.4 (2011-03-01) +---------------- + +* Better unicode support. +* Added ``VerifiedHTTPSConnection``. +* Added ``NTLMConnectionPool`` in contrib. +* Minor improvements. + + +0.3.1 (2010-07-13) +------------------ + +* Added ``assert_host_name`` optional parameter. Now compatible with proxies. + + +0.3 (2009-12-10) +---------------- + +* Added HTTPS support. +* Minor bug fixes. +* Refactored, broken backwards compatibility with 0.2. +* API to be treated as stable from this version forward. + + +0.2 (2008-11-17) +---------------- + +* Added unit tests. +* Bug fixes. + + +0.1 (2008-11-16) +---------------- + +* First release. + + diff --git a/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/RECORD b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..2a54b07881e9eed8c7d59b9e4549bae237437f0d --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/RECORD @@ -0,0 +1,86 @@ +urllib3/__init__.py,sha256=oVOeFuNyEg33ShIefD4qSeGvbb-aJ3otjcdX2iswkLM,2683 +urllib3/_collections.py,sha256=EASnQyCdD28r214TYFl0_AqhAyTkDyn9xvDxhX_4bDM,10782 +urllib3/connection.py,sha256=z6GAPLi2Mj4ko9fEE0z8v9_Aw9yxZl5S_QoEzE5B0BY,13991 +urllib3/connectionpool.py,sha256=I9bSvbCK_RaJg-eWanPhDNeoiyUr-z_KTO1vTWTzfeI,36597 +urllib3/exceptions.py,sha256=m4wjR3tjxpA9UpHDkA17oUveium9Jl9tlq7KiXZNshA,6597 +urllib3/fields.py,sha256=uJD2voWwcbgIUOGpfzHu93vH9h_8YiBMta-Rogp3zQw,8538 +urllib3/filepost.py,sha256=uuQLZmTX7wzByOqkfQblnhZ6Z8NcpQIK5iAFTE9H3IY,2415 +urllib3/poolmanager.py,sha256=ZewLfWn1OE16AxF585MLLEzZ_JHjFf-kNPzWLBAx080,17028 +urllib3/request.py,sha256=kadPA_I1wh0FpFes8v6NNe8ju6IB_C4o7QkpaeyST_A,6008 +urllib3/response.py,sha256=b7o-UR-atlD8igi5eyI-yKwoZaensIIDokvcYfRbCdU,27813 +urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +urllib3/contrib/_appengine_environ.py,sha256=bDbyOEhW2CKLJcQqAKAyrEHN-aklsyHFKq6vF8ZFsmk,957 +urllib3/contrib/appengine.py,sha256=i2JE4PovYjOVf24WJTqyqN1tD23zdETL_dN-heQlzec,10999 +urllib3/contrib/ntlmpool.py,sha256=uY2D_nq3kf1OOFFdjOAwQN0l5KF5V7Jz_EDTUpXlMAM,4149 +urllib3/contrib/pyopenssl.py,sha256=ZMO5enmunSS5PjyMKvg1-pJqxtpQD9IYh5H-p_563Qc,16407 +urllib3/contrib/securetransport.py,sha256=iKzVUAxKnChsADR5YMwc05oEixXDzAk0xPU0g-rc2z8,32275 +urllib3/contrib/socks.py,sha256=nzDMgDIFJWVubKHqvIn2-SKCO91hhJInP92WgHChGzA,7036 +urllib3/contrib/_securetransport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +urllib3/contrib/_securetransport/bindings.py,sha256=mullWYFaghBdRWla6HYU-TBgFRTPLBEfxj3jplbeJmQ,16886 +urllib3/contrib/_securetransport/low_level.py,sha256=V7GnujxnWZh2N2sMsV5N4d9Imymokkm3zBwgt77_bSE,11956 +urllib3/packages/__init__.py,sha256=7Edo1aneF4-I-RX4-eBILdRL3Ye6HbAQAvwqFGhgp2I,300 +urllib3/packages/backports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +urllib3/packages/backports/makefile.py,sha256=005wrvH-_pWSnTFqQ2sdzzh4zVCtQUUQ4mR2Yyxwc0A,1418 +urllib3/packages/ssl_match_hostname/__init__.py,sha256=ywgKMtfHi1-DrXlzPfVAhzsLzzqcK7GT6eLgdode1Fg,688 +urllib3/packages/ssl_match_hostname/_implementation.py,sha256=6dZ-q074g7XhsJ27MFCgkct8iVNZB3sMZvKhf-KUVy0,5679 +urllib3/util/__init__.py,sha256=bWNaav_OT-1L7-sxm59cGb59rDORlbhb_4noduM5m0U,1038 +urllib3/util/connection.py,sha256=NsxUAKQ98GKywta--zg57CdVpeTCI6N-GElCq78Dl8U,4637 +urllib3/util/queue.py,sha256=JgM-hj60tHkrsABs9zpONo4QNH8kGOTR7GNGRyOi_80,470 +urllib3/util/request.py,sha256=Ohxx1TaEPzXbHQW1tyocLUs_cyBQBVY9PpS4EacigD8,3804 +urllib3/util/response.py,sha256=WC1TEwNHy2GJMNFzjWmXdzxqaoh4XzwTH2qUMvXiQCw,2562 +urllib3/util/retry.py,sha256=xfWluFSKLlm7GS7OZqGoMc0mXHZaheXTKVdkReGIBTU,15434 +urllib3/util/ssl_.py,sha256=Pw1PJRd37VROQdrCEKnsut3Pm_Wd4uoWU8b9gYOG6Qo,14139 +urllib3/util/timeout.py,sha256=bCtaS_xVKaTDJ5VMlroXBfCnPUDNVGZqik7-z83issg,9871 +urllib3/util/url.py,sha256=XT8PO3bNzd6R_Fx6WTbs6U6yXEtwjJgobj0H7lCgzWg,13946 +urllib3/util/wait.py,sha256=k46KzqIYu3Vnzla5YW3EvtInNlU_QycFqQAghIOxoAg,5406 +urllib3-1.25.8.dist-info/AUTHORS.txt,sha256=RtqU9KfonVGhI48DAA4-yTOBUhBtQTjFhaDzHoyh7uU,21518 +urllib3-1.25.8.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090 +urllib3-1.25.8.dist-info/METADATA,sha256=AMgz2NLN1OvTguiH_nd51xQJWq6iuAbufr32v2R57Ik,38976 +urllib3-1.25.8.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +urllib3-1.25.8.dist-info/top_level.txt,sha256=EMiXL2sKrTcmrMxIHTqdc3ET54pQI2Y072LexFEemvo,8 +urllib3-1.25.8.dist-info/RECORD,, +urllib3/util/timeout.cpython-38.pyc,, +urllib3/packages/backports/__init__.cpython-38.pyc,, +urllib3/util/__init__.cpython-38.pyc,, +urllib3/contrib/_securetransport/bindings.cpython-38.pyc,, +urllib3/contrib/__init__.cpython-38.pyc,, +urllib3/contrib/securetransport.cpython-38.pyc,, +urllib3/packages/__pycache__,, +urllib3/util/url.cpython-38.pyc,, +urllib3/contrib/appengine.cpython-38.pyc,, +urllib3/packages/ssl_match_hostname/__init__.cpython-38.pyc,, +urllib3-1.25.8.dist-info/__pycache__,, +urllib3/connectionpool.cpython-38.pyc,, +urllib3/connection.cpython-38.pyc,, +urllib3/contrib/_securetransport/__init__.cpython-38.pyc,, +urllib3/packages/backports/__pycache__,, +urllib3/util/__pycache__,, +urllib3/_collections.cpython-38.pyc,, +urllib3/util/request.cpython-38.pyc,, +urllib3/contrib/__pycache__,, +urllib3/util/ssl_.cpython-38.pyc,, +urllib3/util/retry.cpython-38.pyc,, +urllib3/packages/backports/makefile.cpython-38.pyc,, +urllib3/response.cpython-38.pyc,, +urllib3-1.25.8.dist-info/INSTALLER,, +urllib3/packages/ssl_match_hostname/__pycache__,, +urllib3/poolmanager.cpython-38.pyc,, +urllib3-1.25.8.virtualenv,, +urllib3/contrib/_securetransport/__pycache__,, +urllib3/filepost.cpython-38.pyc,, +urllib3/util/connection.cpython-38.pyc,, +urllib3/__init__.cpython-38.pyc,, +urllib3/util/queue.cpython-38.pyc,, +urllib3/contrib/socks.cpython-38.pyc,, +urllib3/contrib/pyopenssl.cpython-38.pyc,, +urllib3/contrib/_securetransport/low_level.cpython-38.pyc,, +urllib3/util/response.cpython-38.pyc,, +urllib3/packages/ssl_match_hostname/_implementation.cpython-38.pyc,, +urllib3/fields.cpython-38.pyc,, +urllib3/exceptions.cpython-38.pyc,, +urllib3/contrib/_appengine_environ.cpython-38.pyc,, +urllib3/util/wait.cpython-38.pyc,, +urllib3/packages/__init__.cpython-38.pyc,, +urllib3/contrib/ntlmpool.cpython-38.pyc,, +urllib3/__pycache__,, +urllib3/request.cpython-38.pyc,, \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/WHEEL b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ef99c6cf3283b50a273ac4c6d009a0aa85597070 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..a42590bebea6002a3c81cdbf89443ca1c3578d0b --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3-1.25.8.dist-info/top_level.txt @@ -0,0 +1 @@ +urllib3 diff --git a/robot/lib/python3.8/site-packages/urllib3-1.25.8.virtualenv b/robot/lib/python3.8/site-packages/urllib3-1.25.8.virtualenv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/urllib3/__init__.py b/robot/lib/python3.8/site-packages/urllib3/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9bd8323f91e8daf72b841633f4706a0167c37330 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/__init__.py @@ -0,0 +1,86 @@ +""" +urllib3 - Thread-safe connection pooling and re-using. +""" +from __future__ import absolute_import +import warnings + +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url + +from . import exceptions +from .filepost import encode_multipart_formdata +from .poolmanager import PoolManager, ProxyManager, proxy_from_url +from .response import HTTPResponse +from .util.request import make_headers +from .util.url import get_host +from .util.timeout import Timeout +from .util.retry import Retry + + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +__author__ = "Andrey Petrov (andrey.petrov@shazow.net)" +__license__ = "MIT" +__version__ = "1.25.8" + +__all__ = ( + "HTTPConnectionPool", + "HTTPSConnectionPool", + "PoolManager", + "ProxyManager", + "HTTPResponse", + "Retry", + "Timeout", + "add_stderr_logger", + "connection_from_url", + "disable_warnings", + "encode_multipart_formdata", + "get_host", + "make_headers", + "proxy_from_url", +) + +logging.getLogger(__name__).addHandler(NullHandler()) + + +def add_stderr_logger(level=logging.DEBUG): + """ + Helper for quickly adding a StreamHandler to the logger. Useful for + debugging. + + Returns the handler after adding it. + """ + # This method needs to be in this __init__.py to get the __name__ correct + # even if urllib3 is vendored within another package. + logger = logging.getLogger(__name__) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) + logger.addHandler(handler) + logger.setLevel(level) + logger.debug("Added a stderr logging handler to logger: %s", __name__) + return handler + + +# ... Clean up. +del NullHandler + + +# All warning filters *must* be appended unless you're really certain that they +# shouldn't be: otherwise, it's very hard for users to use most Python +# mechanisms to silence them. +# SecurityWarning's always go off by default. +warnings.simplefilter("always", exceptions.SecurityWarning, append=True) +# SubjectAltNameWarning's should go off once per host +warnings.simplefilter("default", exceptions.SubjectAltNameWarning, append=True) +# InsecurePlatformWarning's don't vary between requests, so we keep it default. +warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) +# SNIMissingWarnings should go off only once. +warnings.simplefilter("default", exceptions.SNIMissingWarning, append=True) + + +def disable_warnings(category=exceptions.HTTPWarning): + """ + Helper for quickly disabling all urllib3 warnings. + """ + warnings.simplefilter("ignore", category) diff --git a/robot/lib/python3.8/site-packages/urllib3/_collections.py b/robot/lib/python3.8/site-packages/urllib3/_collections.py new file mode 100644 index 0000000000000000000000000000000000000000..b84a0ffa9cb11643bda0a0859b9f2f3474a996ae --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/_collections.py @@ -0,0 +1,336 @@ +from __future__ import absolute_import + +try: + from collections.abc import Mapping, MutableMapping +except ImportError: + from collections import Mapping, MutableMapping +try: + from threading import RLock +except ImportError: # Platform-specific: No threads available + + class RLock: + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + pass + + +from collections import OrderedDict +from .exceptions import InvalidHeader +from six import iterkeys, itervalues, PY3 + + +__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] + + +_Null = object() + + +class RecentlyUsedContainer(MutableMapping): + """ + Provides a thread-safe dict-like container which maintains up to + ``maxsize`` keys while throwing away the least-recently-used keys beyond + ``maxsize``. + + :param maxsize: + Maximum number of recent elements to retain. + + :param dispose_func: + Every time an item is evicted from the container, + ``dispose_func(value)`` is called. Callback which will get called + """ + + ContainerCls = OrderedDict + + def __init__(self, maxsize=10, dispose_func=None): + self._maxsize = maxsize + self.dispose_func = dispose_func + + self._container = self.ContainerCls() + self.lock = RLock() + + def __getitem__(self, key): + # Re-insert the item, moving it to the end of the eviction line. + with self.lock: + item = self._container.pop(key) + self._container[key] = item + return item + + def __setitem__(self, key, value): + evicted_value = _Null + with self.lock: + # Possibly evict the existing value of 'key' + evicted_value = self._container.get(key, _Null) + self._container[key] = value + + # If we didn't evict an existing value, we might have to evict the + # least recently used item from the beginning of the container. + if len(self._container) > self._maxsize: + _key, evicted_value = self._container.popitem(last=False) + + if self.dispose_func and evicted_value is not _Null: + self.dispose_func(evicted_value) + + def __delitem__(self, key): + with self.lock: + value = self._container.pop(key) + + if self.dispose_func: + self.dispose_func(value) + + def __len__(self): + with self.lock: + return len(self._container) + + def __iter__(self): + raise NotImplementedError( + "Iteration over this class is unlikely to be threadsafe." + ) + + def clear(self): + with self.lock: + # Copy pointers to all values, then wipe the mapping + values = list(itervalues(self._container)) + self._container.clear() + + if self.dispose_func: + for value in values: + self.dispose_func(value) + + def keys(self): + with self.lock: + return list(iterkeys(self._container)) + + +class HTTPHeaderDict(MutableMapping): + """ + :param headers: + An iterable of field-value pairs. Must not contain multiple field names + when compared case-insensitively. + + :param kwargs: + Additional field-value pairs to pass in to ``dict.update``. + + A ``dict`` like container for storing HTTP Headers. + + Field names are stored and compared case-insensitively in compliance with + RFC 7230. Iteration provides the first case-sensitive key seen for each + case-insensitive pair. + + Using ``__setitem__`` syntax overwrites fields that compare equal + case-insensitively in order to maintain ``dict``'s api. For fields that + compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` + in a loop. + + If multiple fields that are equal case-insensitively are passed to the + constructor or ``.update``, the behavior is undefined and some will be + lost. + + >>> headers = HTTPHeaderDict() + >>> headers.add('Set-Cookie', 'foo=bar') + >>> headers.add('set-cookie', 'baz=quxx') + >>> headers['content-length'] = '7' + >>> headers['SET-cookie'] + 'foo=bar, baz=quxx' + >>> headers['Content-Length'] + '7' + """ + + def __init__(self, headers=None, **kwargs): + super(HTTPHeaderDict, self).__init__() + self._container = OrderedDict() + if headers is not None: + if isinstance(headers, HTTPHeaderDict): + self._copy_from(headers) + else: + self.extend(headers) + if kwargs: + self.extend(kwargs) + + def __setitem__(self, key, val): + self._container[key.lower()] = [key, val] + return self._container[key.lower()] + + def __getitem__(self, key): + val = self._container[key.lower()] + return ", ".join(val[1:]) + + def __delitem__(self, key): + del self._container[key.lower()] + + def __contains__(self, key): + return key.lower() in self._container + + def __eq__(self, other): + if not isinstance(other, Mapping) and not hasattr(other, "keys"): + return False + if not isinstance(other, type(self)): + other = type(self)(other) + return dict((k.lower(), v) for k, v in self.itermerged()) == dict( + (k.lower(), v) for k, v in other.itermerged() + ) + + def __ne__(self, other): + return not self.__eq__(other) + + if not PY3: # Python 2 + iterkeys = MutableMapping.iterkeys + itervalues = MutableMapping.itervalues + + __marker = object() + + def __len__(self): + return len(self._container) + + def __iter__(self): + # Only provide the originally cased names + for vals in self._container.values(): + yield vals[0] + + def pop(self, key, default=__marker): + """D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + """ + # Using the MutableMapping function directly fails due to the private marker. + # Using ordinary dict.pop would expose the internal structures. + # So let's reinvent the wheel. + try: + value = self[key] + except KeyError: + if default is self.__marker: + raise + return default + else: + del self[key] + return value + + def discard(self, key): + try: + del self[key] + except KeyError: + pass + + def add(self, key, val): + """Adds a (name, value) pair, doesn't overwrite the value if it already + exists. + + >>> headers = HTTPHeaderDict(foo='bar') + >>> headers.add('Foo', 'baz') + >>> headers['foo'] + 'bar, baz' + """ + key_lower = key.lower() + new_vals = [key, val] + # Keep the common case aka no item present as fast as possible + vals = self._container.setdefault(key_lower, new_vals) + if new_vals is not vals: + vals.append(val) + + def extend(self, *args, **kwargs): + """Generic import function for any type of header-like object. + Adapted version of MutableMapping.update in order to insert items + with self.add instead of self.__setitem__ + """ + if len(args) > 1: + raise TypeError( + "extend() takes at most 1 positional " + "arguments ({0} given)".format(len(args)) + ) + other = args[0] if len(args) >= 1 else () + + if isinstance(other, HTTPHeaderDict): + for key, val in other.iteritems(): + self.add(key, val) + elif isinstance(other, Mapping): + for key in other: + self.add(key, other[key]) + elif hasattr(other, "keys"): + for key in other.keys(): + self.add(key, other[key]) + else: + for key, value in other: + self.add(key, value) + + for key, value in kwargs.items(): + self.add(key, value) + + def getlist(self, key, default=__marker): + """Returns a list of all the values for the named field. Returns an + empty list if the key doesn't exist.""" + try: + vals = self._container[key.lower()] + except KeyError: + if default is self.__marker: + return [] + return default + else: + return vals[1:] + + # Backwards compatibility for httplib + getheaders = getlist + getallmatchingheaders = getlist + iget = getlist + + # Backwards compatibility for http.cookiejar + get_all = getlist + + def __repr__(self): + return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) + + def _copy_from(self, other): + for key in other: + val = other.getlist(key) + if isinstance(val, list): + # Don't need to convert tuples + val = list(val) + self._container[key.lower()] = [key] + val + + def copy(self): + clone = type(self)() + clone._copy_from(self) + return clone + + def iteritems(self): + """Iterate over all header lines, including duplicate ones.""" + for key in self: + vals = self._container[key.lower()] + for val in vals[1:]: + yield vals[0], val + + def itermerged(self): + """Iterate over all headers, merging duplicate ones together.""" + for key in self: + val = self._container[key.lower()] + yield val[0], ", ".join(val[1:]) + + def items(self): + return list(self.iteritems()) + + @classmethod + def from_httplib(cls, message): # Python 2 + """Read headers from a Python 2 httplib message object.""" + # python2.7 does not expose a proper API for exporting multiheaders + # efficiently. This function re-reads raw lines from the message + # object and extracts the multiheaders properly. + obs_fold_continued_leaders = (" ", "\t") + headers = [] + + for line in message.headers: + if line.startswith(obs_fold_continued_leaders): + if not headers: + # We received a header line that starts with OWS as described + # in RFC-7230 S3.2.4. This indicates a multiline header, but + # there exists no previous header to which we can attach it. + raise InvalidHeader( + "Header continuation with no previous header: %s" % line + ) + else: + key, value = headers[-1] + headers[-1] = (key, value + " " + line.strip()) + continue + + key, value = line.split(":", 1) + headers.append((key, value.strip())) + + return cls(headers) diff --git a/robot/lib/python3.8/site-packages/urllib3/connection.py b/robot/lib/python3.8/site-packages/urllib3/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..ba4b1dc6fa15aa538dff5e00dbfac656a41ea4c6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/connection.py @@ -0,0 +1,414 @@ +from __future__ import absolute_import +import datetime +import logging +import os +import socket +from socket import error as SocketError, timeout as SocketTimeout +import warnings +import six +from six.moves.http_client import HTTPConnection as _HTTPConnection +from six.moves.http_client import HTTPException # noqa: F401 + +try: # Compiled with SSL? + import ssl + + BaseSSLError = ssl.SSLError +except (ImportError, AttributeError): # Platform-specific: No SSL. + ssl = None + + class BaseSSLError(BaseException): + pass + + +try: + # Python 3: not a no-op, we're adding this to the namespace so it can be imported. + ConnectionError = ConnectionError +except NameError: + # Python 2 + class ConnectionError(Exception): + pass + + +from .exceptions import ( + NewConnectionError, + ConnectTimeoutError, + SubjectAltNameWarning, + SystemTimeWarning, +) +from .packages.ssl_match_hostname import match_hostname, CertificateError + +from .util.ssl_ import ( + resolve_cert_reqs, + resolve_ssl_version, + assert_fingerprint, + create_urllib3_context, + ssl_wrap_socket, +) + + +from .util import connection + +from ._collections import HTTPHeaderDict + +log = logging.getLogger(__name__) + +port_by_scheme = {"http": 80, "https": 443} + +# When it comes time to update this value as a part of regular maintenance +# (ie test_recent_date is failing) update it to ~6 months before the current date. +RECENT_DATE = datetime.date(2019, 1, 1) + + +class DummyConnection(object): + """Used to detect a failed ConnectionCls import.""" + + pass + + +class HTTPConnection(_HTTPConnection, object): + """ + Based on httplib.HTTPConnection but provides an extra constructor + backwards-compatibility layer between older and newer Pythons. + + Additional keyword parameters are used to configure attributes of the connection. + Accepted parameters include: + + - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` + - ``source_address``: Set the source address for the current connection. + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass:: + + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). + """ + + default_port = port_by_scheme["http"] + + #: Disable Nagle's algorithm by default. + #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` + default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] + + #: Whether this connection verifies the host's certificate. + is_verified = False + + def __init__(self, *args, **kw): + if not six.PY2: + kw.pop("strict", None) + + # Pre-set source_address. + self.source_address = kw.get("source_address") + + #: The socket options provided by the user. If no options are + #: provided, we use the default options. + self.socket_options = kw.pop("socket_options", self.default_socket_options) + + _HTTPConnection.__init__(self, *args, **kw) + + @property + def host(self): + """ + Getter method to remove any trailing dots that indicate the hostname is an FQDN. + + In general, SSL certificates don't include the trailing dot indicating a + fully-qualified domain name, and thus, they don't validate properly when + checked against a domain name that includes the dot. In addition, some + servers may not expect to receive the trailing dot when provided. + + However, the hostname with trailing dot is critical to DNS resolution; doing a + lookup with the trailing dot will properly only resolve the appropriate FQDN, + whereas a lookup without a trailing dot will search the system's search domain + list. Thus, it's important to keep the original host around for use only in + those cases where it's appropriate (i.e., when doing DNS lookup to establish the + actual TCP connection across which we're going to send HTTP requests). + """ + return self._dns_host.rstrip(".") + + @host.setter + def host(self, value): + """ + Setter for the `host` property. + + We assume that only urllib3 uses the _dns_host attribute; httplib itself + only uses `host`, and it seems reasonable that other libraries follow suit. + """ + self._dns_host = value + + def _new_conn(self): + """ Establish a socket connection and set nodelay settings on it. + + :return: New socket connection. + """ + extra_kw = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + + try: + conn = connection.create_connection( + (self._dns_host, self.port), self.timeout, **extra_kw + ) + + except SocketTimeout: + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) + + except SocketError as e: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) + + return conn + + def _prepare_conn(self, conn): + self.sock = conn + # Google App Engine's httplib does not define _tunnel_host + if getattr(self, "_tunnel_host", None): + # TODO: Fix tunnel so it doesn't depend on self.sock state. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) + + def request_chunked(self, method, url, body=None, headers=None): + """ + Alternative to the common request method, which sends the + body with chunked encoding and not as one block + """ + headers = HTTPHeaderDict(headers if headers is not None else {}) + skip_accept_encoding = "accept-encoding" in headers + skip_host = "host" in headers + self.putrequest( + method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host + ) + for header, value in headers.items(): + self.putheader(header, value) + if "transfer-encoding" not in headers: + self.putheader("Transfer-Encoding", "chunked") + self.endheaders() + + if body is not None: + stringish_types = six.string_types + (bytes,) + if isinstance(body, stringish_types): + body = (body,) + for chunk in body: + if not chunk: + continue + if not isinstance(chunk, bytes): + chunk = chunk.encode("utf8") + len_str = hex(len(chunk))[2:] + self.send(len_str.encode("utf-8")) + self.send(b"\r\n") + self.send(chunk) + self.send(b"\r\n") + + # After the if clause, to always have a closed body + self.send(b"0\r\n\r\n") + + +class HTTPSConnection(HTTPConnection): + default_port = port_by_scheme["https"] + + ssl_version = None + + def __init__( + self, + host, + port=None, + key_file=None, + cert_file=None, + key_password=None, + strict=None, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + ssl_context=None, + server_hostname=None, + **kw + ): + + HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw) + + self.key_file = key_file + self.cert_file = cert_file + self.key_password = key_password + self.ssl_context = ssl_context + self.server_hostname = server_hostname + + # Required property for Google AppEngine 1.9.0 which otherwise causes + # HTTPS requests to go out as HTTP. (See Issue #356) + self._protocol = "https" + + +class VerifiedHTTPSConnection(HTTPSConnection): + """ + Based on httplib.HTTPSConnection but wraps the socket with + SSL certification. + """ + + cert_reqs = None + ca_certs = None + ca_cert_dir = None + ssl_version = None + assert_fingerprint = None + + def set_cert( + self, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + ): + """ + This method should only be called once, before the connection is used. + """ + # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also + # have an SSLContext object in which case we'll use its verify_mode. + if cert_reqs is None: + if self.ssl_context is not None: + cert_reqs = self.ssl_context.verify_mode + else: + cert_reqs = resolve_cert_reqs(None) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + + def connect(self): + # Add certificate verification + conn = self._new_conn() + hostname = self.host + + # Google App Engine's httplib does not define _tunnel_host + if getattr(self, "_tunnel_host", None): + self.sock = conn + # Calls self._set_hostport(), so self.host is + # self._tunnel_host below. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + # Override the host with the one we're requesting data from. + hostname = self._tunnel_host + + server_hostname = hostname + if self.server_hostname is not None: + server_hostname = self.server_hostname + + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn( + ( + "System time is way off (before {0}). This will probably " + "lead to SSL verification errors" + ).format(RECENT_DATE), + SystemTimeWarning, + ) + + # Wrap socket using verification with the root certs in + # trusted_root_certs + default_ssl_context = False + if self.ssl_context is None: + default_ssl_context = True + self.ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(self.ssl_version), + cert_reqs=resolve_cert_reqs(self.cert_reqs), + ) + + context = self.ssl_context + context.verify_mode = resolve_cert_reqs(self.cert_reqs) + + # Try to load OS default certs if none are given. + # Works well on Windows (requires Python3.4+) + if ( + not self.ca_certs + and not self.ca_cert_dir + and default_ssl_context + and hasattr(context, "load_default_certs") + ): + context.load_default_certs() + + self.sock = ssl_wrap_socket( + sock=conn, + keyfile=self.key_file, + certfile=self.cert_file, + key_password=self.key_password, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + server_hostname=server_hostname, + ssl_context=context, + ) + + if self.assert_fingerprint: + assert_fingerprint( + self.sock.getpeercert(binary_form=True), self.assert_fingerprint + ) + elif ( + context.verify_mode != ssl.CERT_NONE + and not getattr(context, "check_hostname", False) + and self.assert_hostname is not False + ): + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = self.sock.getpeercert() + if not cert.get("subjectAltName", ()): + warnings.warn( + ( + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) + ), + SubjectAltNameWarning, + ) + _match_hostname(cert, self.assert_hostname or server_hostname) + + self.is_verified = ( + context.verify_mode == ssl.CERT_REQUIRED + or self.assert_fingerprint is not None + ) + + +def _match_hostname(cert, asserted_hostname): + try: + match_hostname(cert, asserted_hostname) + except CertificateError as e: + log.warning( + "Certificate did not match expected hostname: %s. Certificate: %s", + asserted_hostname, + cert, + ) + # Add cert to exception and reraise so client code can inspect + # the cert when catching the exception, if they want to + e._peer_cert = cert + raise + + +if ssl: + # Make a copy for testing. + UnverifiedHTTPSConnection = HTTPSConnection + HTTPSConnection = VerifiedHTTPSConnection +else: + HTTPSConnection = DummyConnection diff --git a/robot/lib/python3.8/site-packages/urllib3/connectionpool.py b/robot/lib/python3.8/site-packages/urllib3/connectionpool.py new file mode 100644 index 0000000000000000000000000000000000000000..d42384ee1afe7ea8543fb95bf19160b270447368 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/connectionpool.py @@ -0,0 +1,1053 @@ +from __future__ import absolute_import +import errno +import logging +import sys +import warnings + +from socket import error as SocketError, timeout as SocketTimeout +import socket + + +from .exceptions import ( + ClosedPoolError, + ProtocolError, + EmptyPoolError, + HeaderParsingError, + HostChangedError, + LocationValueError, + MaxRetryError, + ProxyError, + ReadTimeoutError, + SSLError, + TimeoutError, + InsecureRequestWarning, + NewConnectionError, +) +from .packages.ssl_match_hostname import CertificateError +import six +from six.moves import queue +from .connection import ( + port_by_scheme, + DummyConnection, + HTTPConnection, + HTTPSConnection, + VerifiedHTTPSConnection, + HTTPException, + BaseSSLError, +) +from .request import RequestMethods +from .response import HTTPResponse + +from .util.connection import is_connection_dropped +from .util.request import set_file_position +from .util.response import assert_header_parsing +from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import ( + get_host, + parse_url, + Url, + _normalize_host as normalize_host, + _encode_target, +) +from .util.queue import LifoQueue + + +xrange = six.moves.xrange + +log = logging.getLogger(__name__) + +_Default = object() + + +# Pool objects +class ConnectionPool(object): + """ + Base class for all connection pools, such as + :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. + """ + + scheme = None + QueueCls = LifoQueue + + def __init__(self, host, port=None): + if not host: + raise LocationValueError("No host specified.") + + self.host = _normalize_host(host, scheme=self.scheme) + self._proxy_host = host.lower() + self.port = port + + def __str__(self): + return "%s(host=%r, port=%r)" % (type(self).__name__, self.host, self.port) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + # Return False to re-raise any potential exceptions + return False + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + pass + + +# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 +_blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK} + + +class HTTPConnectionPool(ConnectionPool, RequestMethods): + """ + Thread-safe connection pool for one host. + + :param host: + Host used for this HTTP Connection (e.g. "localhost"), passed into + :class:`httplib.HTTPConnection`. + + :param port: + Port used for this HTTP Connection (None is equivalent to 80), passed + into :class:`httplib.HTTPConnection`. + + :param strict: + Causes BadStatusLine to be raised if the status line can't be parsed + as a valid HTTP/1.0 or 1.1 status line, passed into + :class:`httplib.HTTPConnection`. + + .. note:: + Only works in Python 2. This parameter is ignored in Python 3. + + :param timeout: + Socket timeout in seconds for each individual connection. This can + be a float or integer, which sets the timeout for the HTTP request, + or an instance of :class:`urllib3.util.Timeout` which gives you more + fine-grained control over request timeouts. After the constructor has + been parsed, this is always a `urllib3.util.Timeout` object. + + :param maxsize: + Number of connections to save that can be reused. More than 1 is useful + in multithreaded situations. If ``block`` is set to False, more + connections will be created but they will not be saved once they've + been used. + + :param block: + If set to True, no more than ``maxsize`` connections will be used at + a time. When no free connections are available, the call will block + until a connection has been released. This is a useful side effect for + particular multithreaded situations where one does not want to use more + than maxsize connections per host to prevent flooding. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param retries: + Retry configuration to use by default with requests in this pool. + + :param _proxy: + Parsed proxy URL, should not be used directly, instead, see + :class:`urllib3.connectionpool.ProxyManager`" + + :param _proxy_headers: + A dictionary with proxy headers, should not be used directly, + instead, see :class:`urllib3.connectionpool.ProxyManager`" + + :param \\**conn_kw: + Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, + :class:`urllib3.connection.HTTPSConnection` instances. + """ + + scheme = "http" + ConnectionCls = HTTPConnection + ResponseCls = HTTPResponse + + def __init__( + self, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + **conn_kw + ): + ConnectionPool.__init__(self, host, port) + RequestMethods.__init__(self, headers) + + self.strict = strict + + if not isinstance(timeout, Timeout): + timeout = Timeout.from_float(timeout) + + if retries is None: + retries = Retry.DEFAULT + + self.timeout = timeout + self.retries = retries + + self.pool = self.QueueCls(maxsize) + self.block = block + + self.proxy = _proxy + self.proxy_headers = _proxy_headers or {} + + # Fill the queue up so that doing get() on it will block properly + for _ in xrange(maxsize): + self.pool.put(None) + + # These are mostly for testing and debugging purposes. + self.num_connections = 0 + self.num_requests = 0 + self.conn_kw = conn_kw + + if self.proxy: + # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. + # We cannot know if the user has added default socket options, so we cannot replace the + # list. + self.conn_kw.setdefault("socket_options", []) + + def _new_conn(self): + """ + Return a fresh :class:`HTTPConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTP connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "80", + ) + + conn = self.ConnectionCls( + host=self.host, + port=self.port, + timeout=self.timeout.connect_timeout, + strict=self.strict, + **self.conn_kw + ) + return conn + + def _get_conn(self, timeout=None): + """ + Get a connection. Will return a pooled connection if one is available. + + If no connections are available and :prop:`.block` is ``False``, then a + fresh connection is returned. + + :param timeout: + Seconds to wait before giving up and raising + :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and + :prop:`.block` is ``True``. + """ + conn = None + try: + conn = self.pool.get(block=self.block, timeout=timeout) + + except AttributeError: # self.pool is None + raise ClosedPoolError(self, "Pool is closed.") + + except queue.Empty: + if self.block: + raise EmptyPoolError( + self, + "Pool reached maximum size and no more connections are allowed.", + ) + pass # Oh well, we'll create a new connection then + + # If this is a persistent connection, check if it got disconnected + if conn and is_connection_dropped(conn): + log.debug("Resetting dropped connection: %s", self.host) + conn.close() + if getattr(conn, "auto_open", 1) == 0: + # This is a proxied connection that has been mutated by + # httplib._tunnel() and cannot be reused (since it would + # attempt to bypass the proxy) + conn = None + + return conn or self._new_conn() + + def _put_conn(self, conn): + """ + Put a connection back into the pool. + + :param conn: + Connection object for the current host and port as returned by + :meth:`._new_conn` or :meth:`._get_conn`. + + If the pool is already full, the connection is closed and discarded + because we exceeded maxsize. If connections are discarded frequently, + then maxsize should be increased. + + If the pool is closed, then the connection will be closed and discarded. + """ + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except queue.Full: + # This should never happen if self.block == True + log.warning("Connection pool is full, discarding connection: %s", self.host) + + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + pass + + def _prepare_proxy(self, conn): + # Nothing to do for HTTP connections. + pass + + def _get_timeout(self, timeout): + """ Helper that always returns a :class:`urllib3.util.Timeout` """ + if timeout is _Default: + return self.timeout.clone() + + if isinstance(timeout, Timeout): + return timeout.clone() + else: + # User passed us an int/float. This is for backwards compatibility, + # can be removed later + return Timeout.from_float(timeout) + + def _raise_timeout(self, err, url, timeout_value): + """Is the error actually a timeout? Will raise a ReadTimeout or pass""" + + if isinstance(err, SocketTimeout): + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + # See the above comment about EAGAIN in Python 3. In Python 2 we have + # to specifically catch it and throw the timeout error + if hasattr(err, "errno") and err.errno in _blocking_errnos: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + # Catch possible read timeouts thrown as SSL errors. If not the + # case, rethrow the original. We need to do this because of: + # http://bugs.python.org/issue10272 + if "timed out" in str(err) or "did not complete (read)" in str( + err + ): # Python < 2.7.4 + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + def _make_request( + self, conn, method, url, timeout=_Default, chunked=False, **httplib_request_kw + ): + """ + Perform a request on a given urllib connection object taken from our + pool. + + :param conn: + a connection from one of our connection pools + + :param timeout: + Socket timeout in seconds for the request. This can be a + float or integer, which will set the same timeout value for + the socket connect and the socket read, or an instance of + :class:`urllib3.util.Timeout`, which gives you more fine-grained + control over your timeouts. + """ + self.num_requests += 1 + + timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = timeout_obj.connect_timeout + + # Trigger any extra validation we need to do. + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise + + # conn.request() calls httplib.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + if chunked: + conn.request_chunked(method, url, **httplib_request_kw) + else: + conn.request(method, url, **httplib_request_kw) + + # Reset the timeout for the recv() on the socket + read_timeout = timeout_obj.read_timeout + + # App Engine doesn't have a sock attr + if getattr(conn, "sock", None): + # In Python 3 socket.py will catch EAGAIN and return None when you + # try and read into the file pointer created by http.client, which + # instead raises a BadStatusLine exception. Instead of catching + # the exception and assuming all BadStatusLine exceptions are read + # timeouts, check for a zero timeout before making the request. + if read_timeout == 0: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % read_timeout + ) + if read_timeout is Timeout.DEFAULT_TIMEOUT: + conn.sock.settimeout(socket.getdefaulttimeout()) + else: # None or a value + conn.sock.settimeout(read_timeout) + + # Receive the response from the server + try: + try: + # Python 2.7, use buffering of HTTP responses + httplib_response = conn.getresponse(buffering=True) + except TypeError: + # Python 3 + try: + httplib_response = conn.getresponse() + except BaseException as e: + # Remove the TypeError from the exception chain in + # Python 3 (including for exceptions like SystemExit). + # Otherwise it looks like a bug in the code. + six.raise_from(e, None) + except (SocketTimeout, BaseSSLError, SocketError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) + raise + + # AppEngine doesn't have a version attr. + http_version = getattr(conn, "_http_vsn_str", "HTTP/?") + log.debug( + '%s://%s:%s "%s %s %s" %s %s', + self.scheme, + self.host, + self.port, + method, + url, + http_version, + httplib_response.status, + httplib_response.length, + ) + + try: + assert_header_parsing(httplib_response.msg) + except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 + log.warning( + "Failed to parse headers (url=%s): %s", + self._absolute_url(url), + hpe, + exc_info=True, + ) + + return httplib_response + + def _absolute_url(self, path): + return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + if self.pool is None: + return + # Disable access to the pool + old_pool, self.pool = self.pool, None + + try: + while True: + conn = old_pool.get(block=False) + if conn: + conn.close() + + except queue.Empty: + pass # Done. + + def is_same_host(self, url): + """ + Check if the given ``url`` is a member of the same host as this + connection pool. + """ + if url.startswith("/"): + return True + + # TODO: Add optional support for socket.gethostbyname checking. + scheme, host, port = get_host(url) + if host is not None: + host = _normalize_host(host, scheme=scheme) + + # Use explicit default port for comparison when none is given + if self.port and not port: + port = port_by_scheme.get(scheme) + elif not self.port and port == port_by_scheme.get(scheme): + port = None + + return (scheme, host, port) == (self.scheme, self.host, self.port) + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + assert_same_host=True, + timeout=_Default, + pool_timeout=None, + release_conn=None, + chunked=False, + body_pos=None, + **response_kw + ): + """ + Get a connection from the pool and perform an HTTP request. This is the + lowest level call for making a request, so you'll need to specify all + the raw details. + + .. note:: + + More commonly, it's appropriate to use a convenience method provided + by :class:`.RequestMethods`, such as :meth:`request`. + + .. note:: + + `release_conn` will only behave as expected if + `preload_content=False` because we want to make + `preload_content=False` the default behaviour someday soon without + breaking backwards compatibility. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param body: + Data to send in the request body (useful for creating + POST requests, see HTTPConnectionPool.post_url for + more convenience). + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + Pass ``None`` to retry until you receive a response. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param assert_same_host: + If ``True``, will make sure that the host of the pool requests is + consistent else will raise HostChangedError. When False, you can + use the pool on an HTTP proxy and request foreign hosts. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param pool_timeout: + If set and the pool is set to block=True, then this method will + block for ``pool_timeout`` seconds and raise EmptyPoolError if no + connection is available within the time period. + + :param release_conn: + If False, then the urlopen call will not release the connection + back into the pool once a response is received (but will release if + you read the entire contents of the response such as when + `preload_content=True`). This is useful if you're not preloading + the response's content immediately. You will need to call + ``r.release_conn()`` on the response ``r`` to return the connection + back into the pool. If None, it takes the value of + ``response_kw.get('preload_content', True)``. + + :param chunked: + If True, urllib3 will send the body using chunked transfer + encoding. Otherwise, urllib3 will send the body using the standard + content-length form. Defaults to False. + + :param int body_pos: + Position to seek to in file-like body in the event of a retry or + redirect. Typically this won't need to be set because urllib3 will + auto-populate the value when needed. + + :param \\**response_kw: + Additional parameters are passed to + :meth:`urllib3.response.HTTPResponse.from_httplib` + """ + if headers is None: + headers = self.headers + + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if release_conn is None: + release_conn = response_kw.get("preload_content", True) + + # Check host + if assert_same_host and not self.is_same_host(url): + raise HostChangedError(self, url, retries) + + # Ensure that the URL we're connecting to is properly encoded + if url.startswith("/"): + url = six.ensure_str(_encode_target(url)) + else: + url = six.ensure_str(parse_url(url).url) + + conn = None + + # Track whether `conn` needs to be released before + # returning/raising/recursing. Update this variable if necessary, and + # leave `release_conn` constant throughout the function. That way, if + # the function recurses, the original value of `release_conn` will be + # passed down into the recursive call, and its value will be respected. + # + # See issue #651 [1] for details. + # + # [1] + release_this_conn = release_conn + + # Merge the proxy headers. Only do this in HTTP. We have to copy the + # headers dict so we can safely change it without those changes being + # reflected in anyone else's copy. + if self.scheme == "http": + headers = headers.copy() + headers.update(self.proxy_headers) + + # Must keep the exception bound to a separate variable or else Python 3 + # complains about UnboundLocalError. + err = None + + # Keep track of whether we cleanly exited the except block. This + # ensures we do proper cleanup in finally. + clean_exit = False + + # Rewind body position, if needed. Record current position + # for future rewinds in the event of a redirect/retry. + body_pos = set_file_position(body, body_pos) + + try: + # Request a connection from the queue. + timeout_obj = self._get_timeout(timeout) + conn = self._get_conn(timeout=pool_timeout) + + conn.timeout = timeout_obj.connect_timeout + + is_new_proxy_conn = self.proxy is not None and not getattr( + conn, "sock", None + ) + if is_new_proxy_conn: + self._prepare_proxy(conn) + + # Make the request on the httplib connection object. + httplib_response = self._make_request( + conn, + method, + url, + timeout=timeout_obj, + body=body, + headers=headers, + chunked=chunked, + ) + + # If we're going to release the connection in ``finally:``, then + # the response doesn't need to know about the connection. Otherwise + # it will also try to release it and we'll have a double-release + # mess. + response_conn = conn if not release_conn else None + + # Pass method to Response for length checking + response_kw["request_method"] = method + + # Import httplib's response into our own wrapper object + response = self.ResponseCls.from_httplib( + httplib_response, + pool=self, + connection=response_conn, + retries=retries, + **response_kw + ) + + # Everything went great! + clean_exit = True + + except queue.Empty: + # Timed out by queue. + raise EmptyPoolError(self, "No pool connections are available.") + + except ( + TimeoutError, + HTTPException, + SocketError, + ProtocolError, + BaseSSLError, + SSLError, + CertificateError, + ) as e: + # Discard the connection for these exceptions. It will be + # replaced during the next _get_conn() call. + clean_exit = False + if isinstance(e, (BaseSSLError, CertificateError)): + e = SSLError(e) + elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: + e = ProxyError("Cannot connect to proxy.", e) + elif isinstance(e, (SocketError, HTTPException)): + e = ProtocolError("Connection aborted.", e) + + retries = retries.increment( + method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2] + ) + retries.sleep() + + # Keep track of the error for the retry warning. + err = e + + finally: + if not clean_exit: + # We hit some kind of exception, handled or otherwise. We need + # to throw the connection away unless explicitly told not to. + # Close the connection, set the variable to None, and make sure + # we put the None back in the pool to avoid leaking it. + conn = conn and conn.close() + release_this_conn = True + + if release_this_conn: + # Put the connection back to be reused. If the connection is + # expired then it will be None, which will get replaced with a + # fresh connection during _get_conn. + self._put_conn(conn) + + if not conn: + # Try again + log.warning( + "Retrying (%r) after connection broken by '%r': %s", retries, err, url + ) + return self.urlopen( + method, + url, + body, + headers, + retries, + redirect, + assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) + + def drain_and_release_conn(response): + try: + # discard any remaining response body, the connection will be + # released back to the pool once the entire response is read + response.read() + except ( + TimeoutError, + HTTPException, + SocketError, + ProtocolError, + BaseSSLError, + SSLError, + ): + pass + + # Handle redirect? + redirect_location = redirect and response.get_redirect_location() + if redirect_location: + if response.status == 303: + method = "GET" + + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + # Drain and release the connection for this response, since + # we're not returning it to be released manually. + drain_and_release_conn(response) + raise + return response + + # drain and return the connection to the pool before recursing + drain_and_release_conn(response) + + retries.sleep_for_retry(response) + log.debug("Redirecting %s -> %s", url, redirect_location) + return self.urlopen( + method, + redirect_location, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) + + # Check if we should retry the HTTP response. + has_retry_after = bool(response.getheader("Retry-After")) + if retries.is_retry(method, response.status, has_retry_after): + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_status: + # Drain and release the connection for this response, since + # we're not returning it to be released manually. + drain_and_release_conn(response) + raise + return response + + # drain and return the connection to the pool before recursing + drain_and_release_conn(response) + + retries.sleep(response) + log.debug("Retry: %s", url) + return self.urlopen( + method, + url, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) + + return response + + +class HTTPSConnectionPool(HTTPConnectionPool): + """ + Same as :class:`.HTTPConnectionPool`, but HTTPS. + + When Python is compiled with the :mod:`ssl` module, then + :class:`.VerifiedHTTPSConnection` is used, which *can* verify certificates, + instead of :class:`.HTTPSConnection`. + + :class:`.VerifiedHTTPSConnection` uses one of ``assert_fingerprint``, + ``assert_hostname`` and ``host`` in this order to verify connections. + If ``assert_hostname`` is False, no verification is done. + + The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, + ``ca_cert_dir``, ``ssl_version``, ``key_password`` are only used if :mod:`ssl` + is available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade + the connection socket into an SSL socket. + + On Debian, SSL certificate validation is required by default + """ + + scheme = "https" + ConnectionCls = HTTPSConnection + + def __init__( + self, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + key_file=None, + cert_file=None, + cert_reqs='CERT_REQUIRED', + key_password=None, + ca_certs='/etc/ssl/certs/ca-certificates.crt', + ssl_version=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + **conn_kw + ): + + HTTPConnectionPool.__init__( + self, + host, + port, + strict, + timeout, + maxsize, + block, + headers, + retries, + _proxy, + _proxy_headers, + **conn_kw + ) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.ca_certs = ca_certs + self.ca_cert_dir = ca_cert_dir + self.ssl_version = ssl_version + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + + def _prepare_conn(self, conn): + """ + Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` + and establish the tunnel if proxy is used. + """ + + if isinstance(conn, VerifiedHTTPSConnection): + conn.set_cert( + key_file=self.key_file, + key_password=self.key_password, + cert_file=self.cert_file, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ) + conn.ssl_version = self.ssl_version + return conn + + def _prepare_proxy(self, conn): + """ + Establish tunnel connection early, because otherwise httplib + would improperly set Host: header to proxy's IP:port. + """ + conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) + conn.connect() + + def _new_conn(self): + """ + Return a fresh :class:`httplib.HTTPSConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTPS connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "443", + ) + + if not self.ConnectionCls or self.ConnectionCls is DummyConnection: + raise SSLError( + "Can't connect to HTTPS URL because the SSL module is not available." + ) + + actual_host = self.host + actual_port = self.port + if self.proxy is not None: + actual_host = self.proxy.host + actual_port = self.proxy.port + + conn = self.ConnectionCls( + host=actual_host, + port=actual_port, + timeout=self.timeout.connect_timeout, + strict=self.strict, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + **self.conn_kw + ) + + return self._prepare_conn(conn) + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + super(HTTPSConnectionPool, self)._validate_conn(conn) + + # Force connect early to allow us to validate the connection. + if not getattr(conn, "sock", None): # AppEngine might not have `.sock` + conn.connect() + + if not conn.is_verified: + warnings.warn( + ( + "Unverified HTTPS request is being made to host '%s'. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" + "#ssl-warnings" % conn.host + ), + InsecureRequestWarning, + ) + + +def connection_from_url(url, **kw): + """ + Given a url, return an :class:`.ConnectionPool` instance of its host. + + This is a shortcut for not having to parse out the scheme, host, and port + of the url before creating an :class:`.ConnectionPool` instance. + + :param url: + Absolute URL string that must include the scheme. Port is optional. + + :param \\**kw: + Passes additional parameters to the constructor of the appropriate + :class:`.ConnectionPool`. Useful for specifying things like + timeout, maxsize, headers, etc. + + Example:: + + >>> conn = connection_from_url('http://google.com/') + >>> r = conn.request('GET', '/') + """ + scheme, host, port = get_host(url) + port = port or port_by_scheme.get(scheme, 80) + if scheme == "https": + return HTTPSConnectionPool(host, port=port, **kw) + else: + return HTTPConnectionPool(host, port=port, **kw) + + +def _normalize_host(host, scheme): + """ + Normalize hosts for comparisons and use with sockets. + """ + + host = normalize_host(host, scheme) + + # httplib doesn't like it when we include brackets in IPv6 addresses + # Specifically, if we include brackets but also pass the port then + # httplib crazily doubles up the square brackets on the Host header. + # Instead, we need to make sure we never pass ``None`` as the port. + # However, for backward compatibility reasons we can't actually + # *assert* that. See http://bugs.python.org/issue28539 + if host.startswith("[") and host.endswith("]"): + host = host[1:-1] + return host diff --git a/robot/lib/python3.8/site-packages/urllib3/contrib/__init__.py b/robot/lib/python3.8/site-packages/urllib3/contrib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/urllib3/contrib/_appengine_environ.py b/robot/lib/python3.8/site-packages/urllib3/contrib/_appengine_environ.py new file mode 100644 index 0000000000000000000000000000000000000000..8765b907d70c4a530bc90dc88f24b3df73473b01 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/contrib/_appengine_environ.py @@ -0,0 +1,36 @@ +""" +This module provides means to detect the App Engine environment. +""" + +import os + + +def is_appengine(): + return is_local_appengine() or is_prod_appengine() + + +def is_appengine_sandbox(): + """Reports if the app is running in the first generation sandbox. + + The second generation runtimes are technically still in a sandbox, but it + is much less restrictive, so generally you shouldn't need to check for it. + see https://cloud.google.com/appengine/docs/standard/runtimes + """ + return is_appengine() and os.environ["APPENGINE_RUNTIME"] == "python27" + + +def is_local_appengine(): + return "APPENGINE_RUNTIME" in os.environ and os.environ.get( + "SERVER_SOFTWARE", "" + ).startswith("Development/") + + +def is_prod_appengine(): + return "APPENGINE_RUNTIME" in os.environ and os.environ.get( + "SERVER_SOFTWARE", "" + ).startswith("Google App Engine/") + + +def is_prod_appengine_mvms(): + """Deprecated.""" + return False diff --git a/robot/lib/python3.8/site-packages/urllib3/contrib/_securetransport/__init__.py b/robot/lib/python3.8/site-packages/urllib3/contrib/_securetransport/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/urllib3/contrib/_securetransport/bindings.py b/robot/lib/python3.8/site-packages/urllib3/contrib/_securetransport/bindings.py new file mode 100644 index 0000000000000000000000000000000000000000..d9b6733318812d4e72b601c770beff4d4ecd6923 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/contrib/_securetransport/bindings.py @@ -0,0 +1,493 @@ +""" +This module uses ctypes to bind a whole bunch of functions and constants from +SecureTransport. The goal here is to provide the low-level API to +SecureTransport. These are essentially the C-level functions and constants, and +they're pretty gross to work with. + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + + Copyright (c) 2015-2016 Will Bond + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +""" +from __future__ import absolute_import + +import platform +from ctypes.util import find_library +from ctypes import ( + c_void_p, + c_int32, + c_char_p, + c_size_t, + c_byte, + c_uint32, + c_ulong, + c_long, + c_bool, +) +from ctypes import CDLL, POINTER, CFUNCTYPE + + +security_path = find_library("Security") +if not security_path: + raise ImportError("The library Security could not be found") + + +core_foundation_path = find_library("CoreFoundation") +if not core_foundation_path: + raise ImportError("The library CoreFoundation could not be found") + + +version = platform.mac_ver()[0] +version_info = tuple(map(int, version.split("."))) +if version_info < (10, 8): + raise OSError( + "Only OS X 10.8 and newer are supported, not %s.%s" + % (version_info[0], version_info[1]) + ) + +Security = CDLL(security_path, use_errno=True) +CoreFoundation = CDLL(core_foundation_path, use_errno=True) + +Boolean = c_bool +CFIndex = c_long +CFStringEncoding = c_uint32 +CFData = c_void_p +CFString = c_void_p +CFArray = c_void_p +CFMutableArray = c_void_p +CFDictionary = c_void_p +CFError = c_void_p +CFType = c_void_p +CFTypeID = c_ulong + +CFTypeRef = POINTER(CFType) +CFAllocatorRef = c_void_p + +OSStatus = c_int32 + +CFDataRef = POINTER(CFData) +CFStringRef = POINTER(CFString) +CFArrayRef = POINTER(CFArray) +CFMutableArrayRef = POINTER(CFMutableArray) +CFDictionaryRef = POINTER(CFDictionary) +CFArrayCallBacks = c_void_p +CFDictionaryKeyCallBacks = c_void_p +CFDictionaryValueCallBacks = c_void_p + +SecCertificateRef = POINTER(c_void_p) +SecExternalFormat = c_uint32 +SecExternalItemType = c_uint32 +SecIdentityRef = POINTER(c_void_p) +SecItemImportExportFlags = c_uint32 +SecItemImportExportKeyParameters = c_void_p +SecKeychainRef = POINTER(c_void_p) +SSLProtocol = c_uint32 +SSLCipherSuite = c_uint32 +SSLContextRef = POINTER(c_void_p) +SecTrustRef = POINTER(c_void_p) +SSLConnectionRef = c_uint32 +SecTrustResultType = c_uint32 +SecTrustOptionFlags = c_uint32 +SSLProtocolSide = c_uint32 +SSLConnectionType = c_uint32 +SSLSessionOption = c_uint32 + + +try: + Security.SecItemImport.argtypes = [ + CFDataRef, + CFStringRef, + POINTER(SecExternalFormat), + POINTER(SecExternalItemType), + SecItemImportExportFlags, + POINTER(SecItemImportExportKeyParameters), + SecKeychainRef, + POINTER(CFArrayRef), + ] + Security.SecItemImport.restype = OSStatus + + Security.SecCertificateGetTypeID.argtypes = [] + Security.SecCertificateGetTypeID.restype = CFTypeID + + Security.SecIdentityGetTypeID.argtypes = [] + Security.SecIdentityGetTypeID.restype = CFTypeID + + Security.SecKeyGetTypeID.argtypes = [] + Security.SecKeyGetTypeID.restype = CFTypeID + + Security.SecCertificateCreateWithData.argtypes = [CFAllocatorRef, CFDataRef] + Security.SecCertificateCreateWithData.restype = SecCertificateRef + + Security.SecCertificateCopyData.argtypes = [SecCertificateRef] + Security.SecCertificateCopyData.restype = CFDataRef + + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SecIdentityCreateWithCertificate.argtypes = [ + CFTypeRef, + SecCertificateRef, + POINTER(SecIdentityRef), + ] + Security.SecIdentityCreateWithCertificate.restype = OSStatus + + Security.SecKeychainCreate.argtypes = [ + c_char_p, + c_uint32, + c_void_p, + Boolean, + c_void_p, + POINTER(SecKeychainRef), + ] + Security.SecKeychainCreate.restype = OSStatus + + Security.SecKeychainDelete.argtypes = [SecKeychainRef] + Security.SecKeychainDelete.restype = OSStatus + + Security.SecPKCS12Import.argtypes = [ + CFDataRef, + CFDictionaryRef, + POINTER(CFArrayRef), + ] + Security.SecPKCS12Import.restype = OSStatus + + SSLReadFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, c_void_p, POINTER(c_size_t)) + SSLWriteFunc = CFUNCTYPE( + OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t) + ) + + Security.SSLSetIOFuncs.argtypes = [SSLContextRef, SSLReadFunc, SSLWriteFunc] + Security.SSLSetIOFuncs.restype = OSStatus + + Security.SSLSetPeerID.argtypes = [SSLContextRef, c_char_p, c_size_t] + Security.SSLSetPeerID.restype = OSStatus + + Security.SSLSetCertificate.argtypes = [SSLContextRef, CFArrayRef] + Security.SSLSetCertificate.restype = OSStatus + + Security.SSLSetCertificateAuthorities.argtypes = [SSLContextRef, CFTypeRef, Boolean] + Security.SSLSetCertificateAuthorities.restype = OSStatus + + Security.SSLSetConnection.argtypes = [SSLContextRef, SSLConnectionRef] + Security.SSLSetConnection.restype = OSStatus + + Security.SSLSetPeerDomainName.argtypes = [SSLContextRef, c_char_p, c_size_t] + Security.SSLSetPeerDomainName.restype = OSStatus + + Security.SSLHandshake.argtypes = [SSLContextRef] + Security.SSLHandshake.restype = OSStatus + + Security.SSLRead.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] + Security.SSLRead.restype = OSStatus + + Security.SSLWrite.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] + Security.SSLWrite.restype = OSStatus + + Security.SSLClose.argtypes = [SSLContextRef] + Security.SSLClose.restype = OSStatus + + Security.SSLGetNumberSupportedCiphers.argtypes = [SSLContextRef, POINTER(c_size_t)] + Security.SSLGetNumberSupportedCiphers.restype = OSStatus + + Security.SSLGetSupportedCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t), + ] + Security.SSLGetSupportedCiphers.restype = OSStatus + + Security.SSLSetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + c_size_t, + ] + Security.SSLSetEnabledCiphers.restype = OSStatus + + Security.SSLGetNumberEnabledCiphers.argtype = [SSLContextRef, POINTER(c_size_t)] + Security.SSLGetNumberEnabledCiphers.restype = OSStatus + + Security.SSLGetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t), + ] + Security.SSLGetEnabledCiphers.restype = OSStatus + + Security.SSLGetNegotiatedCipher.argtypes = [SSLContextRef, POINTER(SSLCipherSuite)] + Security.SSLGetNegotiatedCipher.restype = OSStatus + + Security.SSLGetNegotiatedProtocolVersion.argtypes = [ + SSLContextRef, + POINTER(SSLProtocol), + ] + Security.SSLGetNegotiatedProtocolVersion.restype = OSStatus + + Security.SSLCopyPeerTrust.argtypes = [SSLContextRef, POINTER(SecTrustRef)] + Security.SSLCopyPeerTrust.restype = OSStatus + + Security.SecTrustSetAnchorCertificates.argtypes = [SecTrustRef, CFArrayRef] + Security.SecTrustSetAnchorCertificates.restype = OSStatus + + Security.SecTrustSetAnchorCertificatesOnly.argstypes = [SecTrustRef, Boolean] + Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus + + Security.SecTrustEvaluate.argtypes = [SecTrustRef, POINTER(SecTrustResultType)] + Security.SecTrustEvaluate.restype = OSStatus + + Security.SecTrustGetCertificateCount.argtypes = [SecTrustRef] + Security.SecTrustGetCertificateCount.restype = CFIndex + + Security.SecTrustGetCertificateAtIndex.argtypes = [SecTrustRef, CFIndex] + Security.SecTrustGetCertificateAtIndex.restype = SecCertificateRef + + Security.SSLCreateContext.argtypes = [ + CFAllocatorRef, + SSLProtocolSide, + SSLConnectionType, + ] + Security.SSLCreateContext.restype = SSLContextRef + + Security.SSLSetSessionOption.argtypes = [SSLContextRef, SSLSessionOption, Boolean] + Security.SSLSetSessionOption.restype = OSStatus + + Security.SSLSetProtocolVersionMin.argtypes = [SSLContextRef, SSLProtocol] + Security.SSLSetProtocolVersionMin.restype = OSStatus + + Security.SSLSetProtocolVersionMax.argtypes = [SSLContextRef, SSLProtocol] + Security.SSLSetProtocolVersionMax.restype = OSStatus + + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SSLReadFunc = SSLReadFunc + Security.SSLWriteFunc = SSLWriteFunc + Security.SSLContextRef = SSLContextRef + Security.SSLProtocol = SSLProtocol + Security.SSLCipherSuite = SSLCipherSuite + Security.SecIdentityRef = SecIdentityRef + Security.SecKeychainRef = SecKeychainRef + Security.SecTrustRef = SecTrustRef + Security.SecTrustResultType = SecTrustResultType + Security.SecExternalFormat = SecExternalFormat + Security.OSStatus = OSStatus + + Security.kSecImportExportPassphrase = CFStringRef.in_dll( + Security, "kSecImportExportPassphrase" + ) + Security.kSecImportItemIdentity = CFStringRef.in_dll( + Security, "kSecImportItemIdentity" + ) + + # CoreFoundation time! + CoreFoundation.CFRetain.argtypes = [CFTypeRef] + CoreFoundation.CFRetain.restype = CFTypeRef + + CoreFoundation.CFRelease.argtypes = [CFTypeRef] + CoreFoundation.CFRelease.restype = None + + CoreFoundation.CFGetTypeID.argtypes = [CFTypeRef] + CoreFoundation.CFGetTypeID.restype = CFTypeID + + CoreFoundation.CFStringCreateWithCString.argtypes = [ + CFAllocatorRef, + c_char_p, + CFStringEncoding, + ] + CoreFoundation.CFStringCreateWithCString.restype = CFStringRef + + CoreFoundation.CFStringGetCStringPtr.argtypes = [CFStringRef, CFStringEncoding] + CoreFoundation.CFStringGetCStringPtr.restype = c_char_p + + CoreFoundation.CFStringGetCString.argtypes = [ + CFStringRef, + c_char_p, + CFIndex, + CFStringEncoding, + ] + CoreFoundation.CFStringGetCString.restype = c_bool + + CoreFoundation.CFDataCreate.argtypes = [CFAllocatorRef, c_char_p, CFIndex] + CoreFoundation.CFDataCreate.restype = CFDataRef + + CoreFoundation.CFDataGetLength.argtypes = [CFDataRef] + CoreFoundation.CFDataGetLength.restype = CFIndex + + CoreFoundation.CFDataGetBytePtr.argtypes = [CFDataRef] + CoreFoundation.CFDataGetBytePtr.restype = c_void_p + + CoreFoundation.CFDictionaryCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + POINTER(CFTypeRef), + CFIndex, + CFDictionaryKeyCallBacks, + CFDictionaryValueCallBacks, + ] + CoreFoundation.CFDictionaryCreate.restype = CFDictionaryRef + + CoreFoundation.CFDictionaryGetValue.argtypes = [CFDictionaryRef, CFTypeRef] + CoreFoundation.CFDictionaryGetValue.restype = CFTypeRef + + CoreFoundation.CFArrayCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + CFIndex, + CFArrayCallBacks, + ] + CoreFoundation.CFArrayCreate.restype = CFArrayRef + + CoreFoundation.CFArrayCreateMutable.argtypes = [ + CFAllocatorRef, + CFIndex, + CFArrayCallBacks, + ] + CoreFoundation.CFArrayCreateMutable.restype = CFMutableArrayRef + + CoreFoundation.CFArrayAppendValue.argtypes = [CFMutableArrayRef, c_void_p] + CoreFoundation.CFArrayAppendValue.restype = None + + CoreFoundation.CFArrayGetCount.argtypes = [CFArrayRef] + CoreFoundation.CFArrayGetCount.restype = CFIndex + + CoreFoundation.CFArrayGetValueAtIndex.argtypes = [CFArrayRef, CFIndex] + CoreFoundation.CFArrayGetValueAtIndex.restype = c_void_p + + CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll( + CoreFoundation, "kCFAllocatorDefault" + ) + CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeArrayCallBacks" + ) + CoreFoundation.kCFTypeDictionaryKeyCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeDictionaryKeyCallBacks" + ) + CoreFoundation.kCFTypeDictionaryValueCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeDictionaryValueCallBacks" + ) + + CoreFoundation.CFTypeRef = CFTypeRef + CoreFoundation.CFArrayRef = CFArrayRef + CoreFoundation.CFStringRef = CFStringRef + CoreFoundation.CFDictionaryRef = CFDictionaryRef + +except (AttributeError): + raise ImportError("Error initializing ctypes") + + +class CFConst(object): + """ + A class object that acts as essentially a namespace for CoreFoundation + constants. + """ + + kCFStringEncodingUTF8 = CFStringEncoding(0x08000100) + + +class SecurityConst(object): + """ + A class object that acts as essentially a namespace for Security constants. + """ + + kSSLSessionOptionBreakOnServerAuth = 0 + + kSSLProtocol2 = 1 + kSSLProtocol3 = 2 + kTLSProtocol1 = 4 + kTLSProtocol11 = 7 + kTLSProtocol12 = 8 + # SecureTransport does not support TLS 1.3 even if there's a constant for it + kTLSProtocol13 = 10 + kTLSProtocolMaxSupported = 999 + + kSSLClientSide = 1 + kSSLStreamType = 0 + + kSecFormatPEMSequence = 10 + + kSecTrustResultInvalid = 0 + kSecTrustResultProceed = 1 + # This gap is present on purpose: this was kSecTrustResultConfirm, which + # is deprecated. + kSecTrustResultDeny = 3 + kSecTrustResultUnspecified = 4 + kSecTrustResultRecoverableTrustFailure = 5 + kSecTrustResultFatalTrustFailure = 6 + kSecTrustResultOtherError = 7 + + errSSLProtocol = -9800 + errSSLWouldBlock = -9803 + errSSLClosedGraceful = -9805 + errSSLClosedNoNotify = -9816 + errSSLClosedAbort = -9806 + + errSSLXCertChainInvalid = -9807 + errSSLCrypto = -9809 + errSSLInternal = -9810 + errSSLCertExpired = -9814 + errSSLCertNotYetValid = -9815 + errSSLUnknownRootCert = -9812 + errSSLNoRootCert = -9813 + errSSLHostNameMismatch = -9843 + errSSLPeerHandshakeFail = -9824 + errSSLPeerUserCancelled = -9839 + errSSLWeakPeerEphemeralDHKey = -9850 + errSSLServerAuthCompleted = -9841 + errSSLRecordOverflow = -9847 + + errSecVerifyFailed = -67808 + errSecNoTrustSettings = -25263 + errSecItemNotFound = -25300 + errSecInvalidTrustSettings = -25262 + + # Cipher suites. We only pick the ones our default cipher string allows. + # Source: https://developer.apple.com/documentation/security/1550981-ssl_cipher_suite_values + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9 + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8 + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 + TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D + TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C + TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D + TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F + TLS_AES_128_GCM_SHA256 = 0x1301 + TLS_AES_256_GCM_SHA384 = 0x1302 + TLS_AES_128_CCM_8_SHA256 = 0x1305 + TLS_AES_128_CCM_SHA256 = 0x1304 diff --git a/robot/lib/python3.8/site-packages/urllib3/contrib/_securetransport/low_level.py b/robot/lib/python3.8/site-packages/urllib3/contrib/_securetransport/low_level.py new file mode 100644 index 0000000000000000000000000000000000000000..e60168cac14d080b45ac693b2ceda1ec59f712b5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/contrib/_securetransport/low_level.py @@ -0,0 +1,328 @@ +""" +Low-level helpers for the SecureTransport bindings. + +These are Python functions that are not directly related to the high-level APIs +but are necessary to get them to work. They include a whole bunch of low-level +CoreFoundation messing about and memory management. The concerns in this module +are almost entirely about trying to avoid memory leaks and providing +appropriate and useful assistance to the higher-level code. +""" +import base64 +import ctypes +import itertools +import re +import os +import ssl +import tempfile + +from .bindings import Security, CoreFoundation, CFConst + + +# This regular expression is used to grab PEM data out of a PEM bundle. +_PEM_CERTS_RE = re.compile( + b"-----BEGIN CERTIFICATE-----\n(.*?)\n-----END CERTIFICATE-----", re.DOTALL +) + + +def _cf_data_from_bytes(bytestring): + """ + Given a bytestring, create a CFData object from it. This CFData object must + be CFReleased by the caller. + """ + return CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, bytestring, len(bytestring) + ) + + +def _cf_dictionary_from_tuples(tuples): + """ + Given a list of Python tuples, create an associated CFDictionary. + """ + dictionary_size = len(tuples) + + # We need to get the dictionary keys and values out in the same order. + keys = (t[0] for t in tuples) + values = (t[1] for t in tuples) + cf_keys = (CoreFoundation.CFTypeRef * dictionary_size)(*keys) + cf_values = (CoreFoundation.CFTypeRef * dictionary_size)(*values) + + return CoreFoundation.CFDictionaryCreate( + CoreFoundation.kCFAllocatorDefault, + cf_keys, + cf_values, + dictionary_size, + CoreFoundation.kCFTypeDictionaryKeyCallBacks, + CoreFoundation.kCFTypeDictionaryValueCallBacks, + ) + + +def _cf_string_to_unicode(value): + """ + Creates a Unicode string from a CFString object. Used entirely for error + reporting. + + Yes, it annoys me quite a lot that this function is this complex. + """ + value_as_void_p = ctypes.cast(value, ctypes.POINTER(ctypes.c_void_p)) + + string = CoreFoundation.CFStringGetCStringPtr( + value_as_void_p, CFConst.kCFStringEncodingUTF8 + ) + if string is None: + buffer = ctypes.create_string_buffer(1024) + result = CoreFoundation.CFStringGetCString( + value_as_void_p, buffer, 1024, CFConst.kCFStringEncodingUTF8 + ) + if not result: + raise OSError("Error copying C string from CFStringRef") + string = buffer.value + if string is not None: + string = string.decode("utf-8") + return string + + +def _assert_no_error(error, exception_class=None): + """ + Checks the return code and throws an exception if there is an error to + report + """ + if error == 0: + return + + cf_error_string = Security.SecCopyErrorMessageString(error, None) + output = _cf_string_to_unicode(cf_error_string) + CoreFoundation.CFRelease(cf_error_string) + + if output is None or output == u"": + output = u"OSStatus %s" % error + + if exception_class is None: + exception_class = ssl.SSLError + + raise exception_class(output) + + +def _cert_array_from_pem(pem_bundle): + """ + Given a bundle of certs in PEM format, turns them into a CFArray of certs + that can be used to validate a cert chain. + """ + # Normalize the PEM bundle's line endings. + pem_bundle = pem_bundle.replace(b"\r\n", b"\n") + + der_certs = [ + base64.b64decode(match.group(1)) for match in _PEM_CERTS_RE.finditer(pem_bundle) + ] + if not der_certs: + raise ssl.SSLError("No root certificates specified") + + cert_array = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cert_array: + raise ssl.SSLError("Unable to allocate memory!") + + try: + for der_bytes in der_certs: + certdata = _cf_data_from_bytes(der_bytes) + if not certdata: + raise ssl.SSLError("Unable to allocate memory!") + cert = Security.SecCertificateCreateWithData( + CoreFoundation.kCFAllocatorDefault, certdata + ) + CoreFoundation.CFRelease(certdata) + if not cert: + raise ssl.SSLError("Unable to build cert object!") + + CoreFoundation.CFArrayAppendValue(cert_array, cert) + CoreFoundation.CFRelease(cert) + except Exception: + # We need to free the array before the exception bubbles further. + # We only want to do that if an error occurs: otherwise, the caller + # should free. + CoreFoundation.CFRelease(cert_array) + + return cert_array + + +def _is_cert(item): + """ + Returns True if a given CFTypeRef is a certificate. + """ + expected = Security.SecCertificateGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _is_identity(item): + """ + Returns True if a given CFTypeRef is an identity. + """ + expected = Security.SecIdentityGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _temporary_keychain(): + """ + This function creates a temporary Mac keychain that we can use to work with + credentials. This keychain uses a one-time password and a temporary file to + store the data. We expect to have one keychain per socket. The returned + SecKeychainRef must be freed by the caller, including calling + SecKeychainDelete. + + Returns a tuple of the SecKeychainRef and the path to the temporary + directory that contains it. + """ + # Unfortunately, SecKeychainCreate requires a path to a keychain. This + # means we cannot use mkstemp to use a generic temporary file. Instead, + # we're going to create a temporary directory and a filename to use there. + # This filename will be 8 random bytes expanded into base64. We also need + # some random bytes to password-protect the keychain we're creating, so we + # ask for 40 random bytes. + random_bytes = os.urandom(40) + filename = base64.b16encode(random_bytes[:8]).decode("utf-8") + password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8 + tempdirectory = tempfile.mkdtemp() + + keychain_path = os.path.join(tempdirectory, filename).encode("utf-8") + + # We now want to create the keychain itself. + keychain = Security.SecKeychainRef() + status = Security.SecKeychainCreate( + keychain_path, len(password), password, False, None, ctypes.byref(keychain) + ) + _assert_no_error(status) + + # Having created the keychain, we want to pass it off to the caller. + return keychain, tempdirectory + + +def _load_items_from_file(keychain, path): + """ + Given a single file, loads all the trust objects from it into arrays and + the keychain. + Returns a tuple of lists: the first list is a list of identities, the + second a list of certs. + """ + certificates = [] + identities = [] + result_array = None + + with open(path, "rb") as f: + raw_filedata = f.read() + + try: + filedata = CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, raw_filedata, len(raw_filedata) + ) + result_array = CoreFoundation.CFArrayRef() + result = Security.SecItemImport( + filedata, # cert data + None, # Filename, leaving it out for now + None, # What the type of the file is, we don't care + None, # what's in the file, we don't care + 0, # import flags + None, # key params, can include passphrase in the future + keychain, # The keychain to insert into + ctypes.byref(result_array), # Results + ) + _assert_no_error(result) + + # A CFArray is not very useful to us as an intermediary + # representation, so we are going to extract the objects we want + # and then free the array. We don't need to keep hold of keys: the + # keychain already has them! + result_count = CoreFoundation.CFArrayGetCount(result_array) + for index in range(result_count): + item = CoreFoundation.CFArrayGetValueAtIndex(result_array, index) + item = ctypes.cast(item, CoreFoundation.CFTypeRef) + + if _is_cert(item): + CoreFoundation.CFRetain(item) + certificates.append(item) + elif _is_identity(item): + CoreFoundation.CFRetain(item) + identities.append(item) + finally: + if result_array: + CoreFoundation.CFRelease(result_array) + + CoreFoundation.CFRelease(filedata) + + return (identities, certificates) + + +def _load_client_cert_chain(keychain, *paths): + """ + Load certificates and maybe keys from a number of files. Has the end goal + of returning a CFArray containing one SecIdentityRef, and then zero or more + SecCertificateRef objects, suitable for use as a client certificate trust + chain. + """ + # Ok, the strategy. + # + # This relies on knowing that macOS will not give you a SecIdentityRef + # unless you have imported a key into a keychain. This is a somewhat + # artificial limitation of macOS (for example, it doesn't necessarily + # affect iOS), but there is nothing inside Security.framework that lets you + # get a SecIdentityRef without having a key in a keychain. + # + # So the policy here is we take all the files and iterate them in order. + # Each one will use SecItemImport to have one or more objects loaded from + # it. We will also point at a keychain that macOS can use to work with the + # private key. + # + # Once we have all the objects, we'll check what we actually have. If we + # already have a SecIdentityRef in hand, fab: we'll use that. Otherwise, + # we'll take the first certificate (which we assume to be our leaf) and + # ask the keychain to give us a SecIdentityRef with that cert's associated + # key. + # + # We'll then return a CFArray containing the trust chain: one + # SecIdentityRef and then zero-or-more SecCertificateRef objects. The + # responsibility for freeing this CFArray will be with the caller. This + # CFArray must remain alive for the entire connection, so in practice it + # will be stored with a single SSLSocket, along with the reference to the + # keychain. + certificates = [] + identities = [] + + # Filter out bad paths. + paths = (path for path in paths if path) + + try: + for file_path in paths: + new_identities, new_certs = _load_items_from_file(keychain, file_path) + identities.extend(new_identities) + certificates.extend(new_certs) + + # Ok, we have everything. The question is: do we have an identity? If + # not, we want to grab one from the first cert we have. + if not identities: + new_identity = Security.SecIdentityRef() + status = Security.SecIdentityCreateWithCertificate( + keychain, certificates[0], ctypes.byref(new_identity) + ) + _assert_no_error(status) + identities.append(new_identity) + + # We now want to release the original certificate, as we no longer + # need it. + CoreFoundation.CFRelease(certificates.pop(0)) + + # We now need to build a new CFArray that holds the trust chain. + trust_chain = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + for item in itertools.chain(identities, certificates): + # ArrayAppendValue does a CFRetain on the item. That's fine, + # because the finally block will release our other refs to them. + CoreFoundation.CFArrayAppendValue(trust_chain, item) + + return trust_chain + finally: + for obj in itertools.chain(identities, certificates): + CoreFoundation.CFRelease(obj) diff --git a/robot/lib/python3.8/site-packages/urllib3/contrib/appengine.py b/robot/lib/python3.8/site-packages/urllib3/contrib/appengine.py new file mode 100644 index 0000000000000000000000000000000000000000..3f98702c9c361912dd4a73f4041d60d8a33145b3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/contrib/appengine.py @@ -0,0 +1,314 @@ +""" +This module provides a pool manager that uses Google App Engine's +`URLFetch Service `_. + +Example usage:: + + from urllib3 import PoolManager + from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox + + if is_appengine_sandbox(): + # AppEngineManager uses AppEngine's URLFetch API behind the scenes + http = AppEngineManager() + else: + # PoolManager uses a socket-level API behind the scenes + http = PoolManager() + + r = http.request('GET', 'https://google.com/') + +There are `limitations `_ to the URLFetch service and it may not be +the best choice for your application. There are three options for using +urllib3 on Google App Engine: + +1. You can use :class:`AppEngineManager` with URLFetch. URLFetch is + cost-effective in many circumstances as long as your usage is within the + limitations. +2. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets. + Sockets also have `limitations and restrictions + `_ and have a lower free quota than URLFetch. + To use sockets, be sure to specify the following in your ``app.yaml``:: + + env_variables: + GAE_USE_SOCKETS_HTTPLIB : 'true' + +3. If you are using `App Engine Flexible +`_, you can use the standard +:class:`PoolManager` without any configuration or special environment variables. +""" + +from __future__ import absolute_import +import io +import logging +import warnings +from six.moves.urllib.parse import urljoin + +from ..exceptions import ( + HTTPError, + HTTPWarning, + MaxRetryError, + ProtocolError, + TimeoutError, + SSLError, +) + +from ..request import RequestMethods +from ..response import HTTPResponse +from ..util.timeout import Timeout +from ..util.retry import Retry +from . import _appengine_environ + +try: + from google.appengine.api import urlfetch +except ImportError: + urlfetch = None + + +log = logging.getLogger(__name__) + + +class AppEnginePlatformWarning(HTTPWarning): + pass + + +class AppEnginePlatformError(HTTPError): + pass + + +class AppEngineManager(RequestMethods): + """ + Connection manager for Google App Engine sandbox applications. + + This manager uses the URLFetch service directly instead of using the + emulated httplib, and is subject to URLFetch limitations as described in + the App Engine documentation `here + `_. + + Notably it will raise an :class:`AppEnginePlatformError` if: + * URLFetch is not available. + * If you attempt to use this on App Engine Flexible, as full socket + support is available. + * If a request size is more than 10 megabytes. + * If a response size is more than 32 megabtyes. + * If you use an unsupported request method such as OPTIONS. + + Beyond those cases, it will raise normal urllib3 errors. + """ + + def __init__( + self, + headers=None, + retries=None, + validate_certificate=True, + urlfetch_retries=True, + ): + if not urlfetch: + raise AppEnginePlatformError( + "URLFetch is not available in this environment." + ) + + warnings.warn( + "urllib3 is using URLFetch on Google App Engine sandbox instead " + "of sockets. To use sockets directly instead of URLFetch see " + "https://urllib3.readthedocs.io/en/latest/reference/urllib3.contrib.html.", + AppEnginePlatformWarning, + ) + + RequestMethods.__init__(self, headers) + self.validate_certificate = validate_certificate + self.urlfetch_retries = urlfetch_retries + + self.retries = retries or Retry.DEFAULT + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + # Return False to re-raise any potential exceptions + return False + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + timeout=Timeout.DEFAULT_TIMEOUT, + **response_kw + ): + + retries = self._get_retries(retries, redirect) + + try: + follow_redirects = redirect and retries.redirect != 0 and retries.total + response = urlfetch.fetch( + url, + payload=body, + method=method, + headers=headers or {}, + allow_truncated=False, + follow_redirects=self.urlfetch_retries and follow_redirects, + deadline=self._get_absolute_timeout(timeout), + validate_certificate=self.validate_certificate, + ) + except urlfetch.DeadlineExceededError as e: + raise TimeoutError(self, e) + + except urlfetch.InvalidURLError as e: + if "too large" in str(e): + raise AppEnginePlatformError( + "URLFetch request too large, URLFetch only " + "supports requests up to 10mb in size.", + e, + ) + raise ProtocolError(e) + + except urlfetch.DownloadError as e: + if "Too many redirects" in str(e): + raise MaxRetryError(self, url, reason=e) + raise ProtocolError(e) + + except urlfetch.ResponseTooLargeError as e: + raise AppEnginePlatformError( + "URLFetch response too large, URLFetch only supports" + "responses up to 32mb in size.", + e, + ) + + except urlfetch.SSLCertificateError as e: + raise SSLError(e) + + except urlfetch.InvalidMethodError as e: + raise AppEnginePlatformError( + "URLFetch does not support method: %s" % method, e + ) + + http_response = self._urlfetch_response_to_http_response( + response, retries=retries, **response_kw + ) + + # Handle redirect? + redirect_location = redirect and http_response.get_redirect_location() + if redirect_location: + # Check for redirect response + if self.urlfetch_retries and retries.raise_on_redirect: + raise MaxRetryError(self, url, "too many redirects") + else: + if http_response.status == 303: + method = "GET" + + try: + retries = retries.increment( + method, url, response=http_response, _pool=self + ) + except MaxRetryError: + if retries.raise_on_redirect: + raise MaxRetryError(self, url, "too many redirects") + return http_response + + retries.sleep_for_retry(http_response) + log.debug("Redirecting %s -> %s", url, redirect_location) + redirect_url = urljoin(url, redirect_location) + return self.urlopen( + method, + redirect_url, + body, + headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) + + # Check if we should retry the HTTP response. + has_retry_after = bool(http_response.getheader("Retry-After")) + if retries.is_retry(method, http_response.status, has_retry_after): + retries = retries.increment(method, url, response=http_response, _pool=self) + log.debug("Retry: %s", url) + retries.sleep(http_response) + return self.urlopen( + method, + url, + body=body, + headers=headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) + + return http_response + + def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): + + if is_prod_appengine(): + # Production GAE handles deflate encoding automatically, but does + # not remove the encoding header. + content_encoding = urlfetch_resp.headers.get("content-encoding") + + if content_encoding == "deflate": + del urlfetch_resp.headers["content-encoding"] + + transfer_encoding = urlfetch_resp.headers.get("transfer-encoding") + # We have a full response's content, + # so let's make sure we don't report ourselves as chunked data. + if transfer_encoding == "chunked": + encodings = transfer_encoding.split(",") + encodings.remove("chunked") + urlfetch_resp.headers["transfer-encoding"] = ",".join(encodings) + + original_response = HTTPResponse( + # In order for decoding to work, we must present the content as + # a file-like object. + body=io.BytesIO(urlfetch_resp.content), + msg=urlfetch_resp.header_msg, + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + **response_kw + ) + + return HTTPResponse( + body=io.BytesIO(urlfetch_resp.content), + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + original_response=original_response, + **response_kw + ) + + def _get_absolute_timeout(self, timeout): + if timeout is Timeout.DEFAULT_TIMEOUT: + return None # Defer to URLFetch's default. + if isinstance(timeout, Timeout): + if timeout._read is not None or timeout._connect is not None: + warnings.warn( + "URLFetch does not support granular timeout settings, " + "reverting to total or default URLFetch timeout.", + AppEnginePlatformWarning, + ) + return timeout.total + return timeout + + def _get_retries(self, retries, redirect): + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if retries.connect or retries.read or retries.redirect: + warnings.warn( + "URLFetch only supports total retries and does not " + "recognize connect, read, or redirect retry parameters.", + AppEnginePlatformWarning, + ) + + return retries + + +# Alias methods from _appengine_environ to maintain public API interface. + +is_appengine = _appengine_environ.is_appengine +is_appengine_sandbox = _appengine_environ.is_appengine_sandbox +is_local_appengine = _appengine_environ.is_local_appengine +is_prod_appengine = _appengine_environ.is_prod_appengine +is_prod_appengine_mvms = _appengine_environ.is_prod_appengine_mvms diff --git a/robot/lib/python3.8/site-packages/urllib3/contrib/ntlmpool.py b/robot/lib/python3.8/site-packages/urllib3/contrib/ntlmpool.py new file mode 100644 index 0000000000000000000000000000000000000000..8f3a9c3c2b146a7f086a3497aaf7aabfe3b3412b --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/contrib/ntlmpool.py @@ -0,0 +1,121 @@ +""" +NTLM authenticating pool, contributed by erikcederstran + +Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 +""" +from __future__ import absolute_import + +from logging import getLogger +from ntlm import ntlm + +from .. import HTTPSConnectionPool +from six.moves.http_client import HTTPSConnection + + +log = getLogger(__name__) + + +class NTLMConnectionPool(HTTPSConnectionPool): + """ + Implements an NTLM authentication version of an urllib3 connection pool + """ + + scheme = "https" + + def __init__(self, user, pw, authurl, *args, **kwargs): + """ + authurl is a random URL on the server that is protected by NTLM. + user is the Windows user, probably in the DOMAIN\\username format. + pw is the password for the user. + """ + super(NTLMConnectionPool, self).__init__(*args, **kwargs) + self.authurl = authurl + self.rawuser = user + user_parts = user.split("\\", 1) + self.domain = user_parts[0].upper() + self.user = user_parts[1] + self.pw = pw + + def _new_conn(self): + # Performs the NTLM handshake that secures the connection. The socket + # must be kept open while requests are performed. + self.num_connections += 1 + log.debug( + "Starting NTLM HTTPS connection no. %d: https://%s%s", + self.num_connections, + self.host, + self.authurl, + ) + + headers = {"Connection": "Keep-Alive"} + req_header = "Authorization" + resp_header = "www-authenticate" + + conn = HTTPSConnection(host=self.host, port=self.port) + + # Send negotiation message + headers[req_header] = "NTLM %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE( + self.rawuser + ) + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) + res = conn.getresponse() + reshdr = dict(res.getheaders()) + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", reshdr) + log.debug("Response data: %s [...]", res.read(100)) + + # Remove the reference to the socket, so that it can not be closed by + # the response object (we want to keep the socket open) + res.fp = None + + # Server should respond with a challenge message + auth_header_values = reshdr[resp_header].split(", ") + auth_header_value = None + for s in auth_header_values: + if s[:5] == "NTLM ": + auth_header_value = s[5:] + if auth_header_value is None: + raise Exception( + "Unexpected %s response header: %s" % (resp_header, reshdr[resp_header]) + ) + + # Send authentication message + ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE( + auth_header_value + ) + auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE( + ServerChallenge, self.user, self.domain, self.pw, NegotiateFlags + ) + headers[req_header] = "NTLM %s" % auth_msg + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) + res = conn.getresponse() + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", dict(res.getheaders())) + log.debug("Response data: %s [...]", res.read()[:100]) + if res.status != 200: + if res.status == 401: + raise Exception("Server rejected request: wrong username or password") + raise Exception("Wrong server response: %s %s" % (res.status, res.reason)) + + res.fp = None + log.debug("Connection established") + return conn + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=3, + redirect=True, + assert_same_host=True, + ): + if headers is None: + headers = {} + headers["Connection"] = "Keep-Alive" + return super(NTLMConnectionPool, self).urlopen( + method, url, body, headers, retries, redirect, assert_same_host + ) diff --git a/robot/lib/python3.8/site-packages/urllib3/contrib/pyopenssl.py b/robot/lib/python3.8/site-packages/urllib3/contrib/pyopenssl.py new file mode 100644 index 0000000000000000000000000000000000000000..2f35eb9410fa9589213f6b17a1191937ecffa462 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/contrib/pyopenssl.py @@ -0,0 +1,498 @@ +""" +SSL with SNI_-support for Python 2. Follow these instructions if you would +like to verify SSL certificates in Python 2. Note, the default libraries do +*not* do certificate checking; you need to do additional work to validate +certificates yourself. + +This needs the following packages installed: + +* pyOpenSSL (tested with 16.0.0) +* cryptography (minimum 1.3.4, from pyopenssl) +* idna (minimum 2.0, from cryptography) + +However, pyopenssl depends on cryptography, which depends on idna, so while we +use all three directly here we end up having relatively few packages required. + +You can install them with the following command: + + pip install pyopenssl cryptography idna + +To activate certificate checking, call +:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code +before you begin making HTTP requests. This can be done in a ``sitecustomize`` +module, or at any other time before your application begins using ``urllib3``, +like this:: + + try: + import urllib3.contrib.pyopenssl + urllib3.contrib.pyopenssl.inject_into_urllib3() + except ImportError: + pass + +Now you can use :mod:`urllib3` as you normally would, and it will support SNI +when the required modules are installed. + +Activating this module also has the positive side effect of disabling SSL/TLS +compression in Python 2 (see `CRIME attack`_). + +If you want to configure the default list of supported cipher suites, you can +set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable. + +.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication +.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) +""" +from __future__ import absolute_import + +import OpenSSL.SSL +from cryptography import x509 +from cryptography.hazmat.backends.openssl import backend as openssl_backend +from cryptography.hazmat.backends.openssl.x509 import _Certificate + +try: + from cryptography.x509 import UnsupportedExtension +except ImportError: + # UnsupportedExtension is gone in cryptography >= 2.1.0 + class UnsupportedExtension(Exception): + pass + + +from socket import timeout, error as SocketError +from io import BytesIO + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +import logging +import ssl +import six +import sys + +from .. import util + + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# SNI always works. +HAS_SNI = True + +# Map from urllib3 to PyOpenSSL compatible parameter-values. +_openssl_versions = { + util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, + ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, +} + +if hasattr(ssl, "PROTOCOL_SSLv3") and hasattr(OpenSSL.SSL, "SSLv3_METHOD"): + _openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD + +if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD + +if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD + + +_stdlib_to_openssl_verify = { + ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, + ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, + ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER + + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, +} +_openssl_to_stdlib_verify = dict((v, k) for k, v in _stdlib_to_openssl_verify.items()) + +# OpenSSL will only write 16K at a time +SSL_WRITE_BLOCKSIZE = 16384 + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + + +log = logging.getLogger(__name__) + + +def inject_into_urllib3(): + "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." + + _validate_dependencies_met() + + util.SSLContext = PyOpenSSLContext + util.ssl_.SSLContext = PyOpenSSLContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_PYOPENSSL = True + util.ssl_.IS_PYOPENSSL = True + + +def extract_from_urllib3(): + "Undo monkey-patching by :func:`inject_into_urllib3`." + + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_PYOPENSSL = False + util.ssl_.IS_PYOPENSSL = False + + +def _validate_dependencies_met(): + """ + Verifies that PyOpenSSL's package-level dependencies have been met. + Throws `ImportError` if they are not met. + """ + # Method added in `cryptography==1.1`; not available in older versions + from cryptography.x509.extensions import Extensions + + if getattr(Extensions, "get_extension_for_class", None) is None: + raise ImportError( + "'cryptography' module missing required functionality. " + "Try upgrading to v1.3.4 or newer." + ) + + # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 + # attribute is only present on those versions. + from OpenSSL.crypto import X509 + + x509 = X509() + if getattr(x509, "_x509", None) is None: + raise ImportError( + "'pyOpenSSL' module missing required functionality. " + "Try upgrading to v0.14 or newer." + ) + + +def _dnsname_to_stdlib(name): + """ + Converts a dNSName SubjectAlternativeName field to the form used by the + standard library on the given Python version. + + Cryptography produces a dNSName as a unicode string that was idna-decoded + from ASCII bytes. We need to idna-encode that string to get it back, and + then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib + uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8). + + If the name cannot be idna-encoded then we return None signalling that + the name given should be skipped. + """ + + def idna_encode(name): + """ + Borrowed wholesale from the Python Cryptography Project. It turns out + that we can't just safely call `idna.encode`: it can explode for + wildcard names. This avoids that problem. + """ + import idna + + try: + for prefix in [u"*.", u"."]: + if name.startswith(prefix): + name = name[len(prefix) :] + return prefix.encode("ascii") + idna.encode(name) + return idna.encode(name) + except idna.core.IDNAError: + return None + + # Don't send IPv6 addresses through the IDNA encoder. + if ":" in name: + return name + + name = idna_encode(name) + if name is None: + return None + elif sys.version_info >= (3, 0): + name = name.decode("utf-8") + return name + + +def get_subj_alt_name(peer_cert): + """ + Given an PyOpenSSL certificate, provides all the subject alternative names. + """ + # Pass the cert to cryptography, which has much better APIs for this. + if hasattr(peer_cert, "to_cryptography"): + cert = peer_cert.to_cryptography() + else: + # This is technically using private APIs, but should work across all + # relevant versions before PyOpenSSL got a proper API for this. + cert = _Certificate(openssl_backend, peer_cert._x509) + + # We want to find the SAN extension. Ask Cryptography to locate it (it's + # faster than looping in Python) + try: + ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value + except x509.ExtensionNotFound: + # No such extension, return the empty list. + return [] + except ( + x509.DuplicateExtension, + UnsupportedExtension, + x509.UnsupportedGeneralNameType, + UnicodeError, + ) as e: + # A problem has been found with the quality of the certificate. Assume + # no SAN field is present. + log.warning( + "A problem was encountered with the certificate that prevented " + "urllib3 from finding the SubjectAlternativeName field. This can " + "affect certificate validation. The error was %s", + e, + ) + return [] + + # We want to return dNSName and iPAddress fields. We need to cast the IPs + # back to strings because the match_hostname function wants them as + # strings. + # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8 + # decoded. This is pretty frustrating, but that's what the standard library + # does with certificates, and so we need to attempt to do the same. + # We also want to skip over names which cannot be idna encoded. + names = [ + ("DNS", name) + for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) + if name is not None + ] + names.extend( + ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress) + ) + + return names + + +class WrappedSocket(object): + """API-compatibility wrapper for Python OpenSSL's Connection-class. + + Note: _makefile_refs, _drop() and _reuse() are needed for the garbage + collector of pypy. + """ + + def __init__(self, connection, socket, suppress_ragged_eofs=True): + self.connection = connection + self.socket = socket + self.suppress_ragged_eofs = suppress_ragged_eofs + self._makefile_refs = 0 + self._closed = False + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, *args, **kwargs): + try: + data = self.connection.recv(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return b"" + else: + raise SocketError(str(e)) + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b"" + else: + raise + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout("The read operation timed out") + else: + return self.recv(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("read error: %r" % e) + else: + return data + + def recv_into(self, *args, **kwargs): + try: + return self.connection.recv_into(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return 0 + else: + raise SocketError(str(e)) + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return 0 + else: + raise + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout("The read operation timed out") + else: + return self.recv_into(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("read error: %r" % e) + + def settimeout(self, timeout): + return self.socket.settimeout(timeout) + + def _send_until_done(self, data): + while True: + try: + return self.connection.send(data) + except OpenSSL.SSL.WantWriteError: + if not util.wait_for_write(self.socket, self.socket.gettimeout()): + raise timeout() + continue + except OpenSSL.SSL.SysCallError as e: + raise SocketError(str(e)) + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self._send_until_done( + data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE] + ) + total_sent += sent + + def shutdown(self): + # FIXME rethrow compatible exceptions should we ever use this + self.connection.shutdown() + + def close(self): + if self._makefile_refs < 1: + try: + self._closed = True + return self.connection.close() + except OpenSSL.SSL.Error: + return + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + x509 = self.connection.get_peer_certificate() + + if not x509: + return x509 + + if binary_form: + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) + + return { + "subject": ((("commonName", x509.get_subject().CN),),), + "subjectAltName": get_subj_alt_name(x509), + } + + def version(self): + return self.connection.get_protocol_version_name() + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) + + +else: # Platform-specific: Python 3 + makefile = backport_makefile + +WrappedSocket.makefile = makefile + + +class PyOpenSSLContext(object): + """ + I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible + for translating the interface of the standard library ``SSLContext`` object + to calls into PyOpenSSL. + """ + + def __init__(self, protocol): + self.protocol = _openssl_versions[protocol] + self._ctx = OpenSSL.SSL.Context(self.protocol) + self._options = 0 + self.check_hostname = False + + @property + def options(self): + return self._options + + @options.setter + def options(self, value): + self._options = value + self._ctx.set_options(value) + + @property + def verify_mode(self): + return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] + + @verify_mode.setter + def verify_mode(self, value): + self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) + + def set_default_verify_paths(self): + self._ctx.set_default_verify_paths() + + def set_ciphers(self, ciphers): + if isinstance(ciphers, six.text_type): + ciphers = ciphers.encode("utf-8") + self._ctx.set_cipher_list(ciphers) + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + if cafile is not None: + cafile = cafile.encode("utf-8") + if capath is not None: + capath = capath.encode("utf-8") + self._ctx.load_verify_locations(cafile, capath) + if cadata is not None: + self._ctx.load_verify_locations(BytesIO(cadata)) + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._ctx.use_certificate_chain_file(certfile) + if password is not None: + if not isinstance(password, six.binary_type): + password = password.encode("utf-8") + self._ctx.set_passwd_cb(lambda *_: password) + self._ctx.use_privatekey_file(keyfile or certfile) + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): + cnx = OpenSSL.SSL.Connection(self._ctx, sock) + + if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 + server_hostname = server_hostname.encode("utf-8") + + if server_hostname is not None: + cnx.set_tlsext_host_name(server_hostname) + + cnx.set_connect_state() + + while True: + try: + cnx.do_handshake() + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(sock, sock.gettimeout()): + raise timeout("select timed out") + continue + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("bad handshake: %r" % e) + break + + return WrappedSocket(cnx, sock) + + +def _verify_callback(cnx, x509, err_no, err_depth, return_code): + return err_no == 0 diff --git a/robot/lib/python3.8/site-packages/urllib3/contrib/securetransport.py b/robot/lib/python3.8/site-packages/urllib3/contrib/securetransport.py new file mode 100644 index 0000000000000000000000000000000000000000..87d844afa78769f0304e4f5953cd3ff8733d0290 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/contrib/securetransport.py @@ -0,0 +1,859 @@ +""" +SecureTranport support for urllib3 via ctypes. + +This makes platform-native TLS available to urllib3 users on macOS without the +use of a compiler. This is an important feature because the Python Package +Index is moving to become a TLSv1.2-or-higher server, and the default OpenSSL +that ships with macOS is not capable of doing TLSv1.2. The only way to resolve +this is to give macOS users an alternative solution to the problem, and that +solution is to use SecureTransport. + +We use ctypes here because this solution must not require a compiler. That's +because pip is not allowed to require a compiler either. + +This is not intended to be a seriously long-term solution to this problem. +The hope is that PEP 543 will eventually solve this issue for us, at which +point we can retire this contrib module. But in the short term, we need to +solve the impending tire fire that is Python on Mac without this kind of +contrib module. So...here we are. + +To use this module, simply import and inject it:: + + import urllib3.contrib.securetransport + urllib3.contrib.securetransport.inject_into_urllib3() + +Happy TLSing! + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + + Copyright (c) 2015-2016 Will Bond + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +""" +from __future__ import absolute_import + +import contextlib +import ctypes +import errno +import os.path +import shutil +import socket +import ssl +import threading +import weakref + +from .. import util +from ._securetransport.bindings import Security, SecurityConst, CoreFoundation +from ._securetransport.low_level import ( + _assert_no_error, + _cert_array_from_pem, + _temporary_keychain, + _load_client_cert_chain, +) + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# SNI always works +HAS_SNI = True + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + +# This dictionary is used by the read callback to obtain a handle to the +# calling wrapped socket. This is a pretty silly approach, but for now it'll +# do. I feel like I should be able to smuggle a handle to the wrapped socket +# directly in the SSLConnectionRef, but for now this approach will work I +# guess. +# +# We need to lock around this structure for inserts, but we don't do it for +# reads/writes in the callbacks. The reasoning here goes as follows: +# +# 1. It is not possible to call into the callbacks before the dictionary is +# populated, so once in the callback the id must be in the dictionary. +# 2. The callbacks don't mutate the dictionary, they only read from it, and +# so cannot conflict with any of the insertions. +# +# This is good: if we had to lock in the callbacks we'd drastically slow down +# the performance of this code. +_connection_refs = weakref.WeakValueDictionary() +_connection_ref_lock = threading.Lock() + +# Limit writes to 16kB. This is OpenSSL's limit, but we'll cargo-cult it over +# for no better reason than we need *a* limit, and this one is right there. +SSL_WRITE_BLOCKSIZE = 16384 + +# This is our equivalent of util.ssl_.DEFAULT_CIPHERS, but expanded out to +# individual cipher suites. We need to do this because this is how +# SecureTransport wants them. +CIPHER_SUITES = [ + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_AES_256_GCM_SHA384, + SecurityConst.TLS_AES_128_GCM_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_AES_128_CCM_8_SHA256, + SecurityConst.TLS_AES_128_CCM_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA, +] + +# Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of +# TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. +# TLSv1 to 1.2 are supported on macOS 10.8+ +_protocol_to_min_max = { + util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12) +} + +if hasattr(ssl, "PROTOCOL_SSLv2"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = ( + SecurityConst.kSSLProtocol2, + SecurityConst.kSSLProtocol2, + ) +if hasattr(ssl, "PROTOCOL_SSLv3"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = ( + SecurityConst.kSSLProtocol3, + SecurityConst.kSSLProtocol3, + ) +if hasattr(ssl, "PROTOCOL_TLSv1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = ( + SecurityConst.kTLSProtocol1, + SecurityConst.kTLSProtocol1, + ) +if hasattr(ssl, "PROTOCOL_TLSv1_1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = ( + SecurityConst.kTLSProtocol11, + SecurityConst.kTLSProtocol11, + ) +if hasattr(ssl, "PROTOCOL_TLSv1_2"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( + SecurityConst.kTLSProtocol12, + SecurityConst.kTLSProtocol12, + ) + + +def inject_into_urllib3(): + """ + Monkey-patch urllib3 with SecureTransport-backed SSL-support. + """ + util.SSLContext = SecureTransportContext + util.ssl_.SSLContext = SecureTransportContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_SECURETRANSPORT = True + util.ssl_.IS_SECURETRANSPORT = True + + +def extract_from_urllib3(): + """ + Undo monkey-patching by :func:`inject_into_urllib3`. + """ + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_SECURETRANSPORT = False + util.ssl_.IS_SECURETRANSPORT = False + + +def _read_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport read callback. This is called by ST to request that data + be returned from the socket. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + requested_length = data_length_pointer[0] + + timeout = wrapped_socket.gettimeout() + error = None + read_count = 0 + + try: + while read_count < requested_length: + if timeout is None or timeout >= 0: + if not util.wait_for_read(base_socket, timeout): + raise socket.error(errno.EAGAIN, "timed out") + + remaining = requested_length - read_count + buffer = (ctypes.c_char * remaining).from_address( + data_buffer + read_count + ) + chunk_size = base_socket.recv_into(buffer, remaining) + read_count += chunk_size + if not chunk_size: + if not read_count: + return SecurityConst.errSSLClosedGraceful + break + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = read_count + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = read_count + + if read_count != requested_length: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +def _write_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport write callback. This is called by ST to request that data + actually be sent on the network. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + bytes_to_write = data_length_pointer[0] + data = ctypes.string_at(data_buffer, bytes_to_write) + + timeout = wrapped_socket.gettimeout() + error = None + sent = 0 + + try: + while sent < bytes_to_write: + if timeout is None or timeout >= 0: + if not util.wait_for_write(base_socket, timeout): + raise socket.error(errno.EAGAIN, "timed out") + chunk_sent = base_socket.send(data) + sent += chunk_sent + + # This has some needless copying here, but I'm not sure there's + # much value in optimising this data path. + data = data[chunk_sent:] + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = sent + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = sent + + if sent != bytes_to_write: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +# We need to keep these two objects references alive: if they get GC'd while +# in use then SecureTransport could attempt to call a function that is in freed +# memory. That would be...uh...bad. Yeah, that's the word. Bad. +_read_callback_pointer = Security.SSLReadFunc(_read_callback) +_write_callback_pointer = Security.SSLWriteFunc(_write_callback) + + +class WrappedSocket(object): + """ + API-compatibility wrapper for Python's OpenSSL wrapped socket object. + + Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage + collector of PyPy. + """ + + def __init__(self, socket): + self.socket = socket + self.context = None + self._makefile_refs = 0 + self._closed = False + self._exception = None + self._keychain = None + self._keychain_dir = None + self._client_cert_chain = None + + # We save off the previously-configured timeout and then set it to + # zero. This is done because we use select and friends to handle the + # timeouts, but if we leave the timeout set on the lower socket then + # Python will "kindly" call select on that socket again for us. Avoid + # that by forcing the timeout to zero. + self._timeout = self.socket.gettimeout() + self.socket.settimeout(0) + + @contextlib.contextmanager + def _raise_on_error(self): + """ + A context manager that can be used to wrap calls that do I/O from + SecureTransport. If any of the I/O callbacks hit an exception, this + context manager will correctly propagate the exception after the fact. + This avoids silently swallowing those exceptions. + + It also correctly forces the socket closed. + """ + self._exception = None + + # We explicitly don't catch around this yield because in the unlikely + # event that an exception was hit in the block we don't want to swallow + # it. + yield + if self._exception is not None: + exception, self._exception = self._exception, None + self.close() + raise exception + + def _set_ciphers(self): + """ + Sets up the allowed ciphers. By default this matches the set in + util.ssl_.DEFAULT_CIPHERS, at least as supported by macOS. This is done + custom and doesn't allow changing at this time, mostly because parsing + OpenSSL cipher strings is going to be a freaking nightmare. + """ + ciphers = (Security.SSLCipherSuite * len(CIPHER_SUITES))(*CIPHER_SUITES) + result = Security.SSLSetEnabledCiphers( + self.context, ciphers, len(CIPHER_SUITES) + ) + _assert_no_error(result) + + def _custom_validate(self, verify, trust_bundle): + """ + Called when we have set custom validation. We do this in two cases: + first, when cert validation is entirely disabled; and second, when + using a custom trust DB. + """ + # If we disabled cert validation, just say: cool. + if not verify: + return + + # We want data in memory, so load it up. + if os.path.isfile(trust_bundle): + with open(trust_bundle, "rb") as f: + trust_bundle = f.read() + + cert_array = None + trust = Security.SecTrustRef() + + try: + # Get a CFArray that contains the certs we want. + cert_array = _cert_array_from_pem(trust_bundle) + + # Ok, now the hard part. We want to get the SecTrustRef that ST has + # created for this connection, shove our CAs into it, tell ST to + # ignore everything else it knows, and then ask if it can build a + # chain. This is a buuuunch of code. + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) + _assert_no_error(result) + if not trust: + raise ssl.SSLError("Failed to copy trust reference") + + result = Security.SecTrustSetAnchorCertificates(trust, cert_array) + _assert_no_error(result) + + result = Security.SecTrustSetAnchorCertificatesOnly(trust, True) + _assert_no_error(result) + + trust_result = Security.SecTrustResultType() + result = Security.SecTrustEvaluate(trust, ctypes.byref(trust_result)) + _assert_no_error(result) + finally: + if trust: + CoreFoundation.CFRelease(trust) + + if cert_array is not None: + CoreFoundation.CFRelease(cert_array) + + # Ok, now we can look at what the result was. + successes = ( + SecurityConst.kSecTrustResultUnspecified, + SecurityConst.kSecTrustResultProceed, + ) + if trust_result.value not in successes: + raise ssl.SSLError( + "certificate verify failed, error code: %d" % trust_result.value + ) + + def handshake( + self, + server_hostname, + verify, + trust_bundle, + min_version, + max_version, + client_cert, + client_key, + client_key_passphrase, + ): + """ + Actually performs the TLS handshake. This is run automatically by + wrapped socket, and shouldn't be needed in user code. + """ + # First, we do the initial bits of connection setup. We need to create + # a context, set its I/O funcs, and set the connection reference. + self.context = Security.SSLCreateContext( + None, SecurityConst.kSSLClientSide, SecurityConst.kSSLStreamType + ) + result = Security.SSLSetIOFuncs( + self.context, _read_callback_pointer, _write_callback_pointer + ) + _assert_no_error(result) + + # Here we need to compute the handle to use. We do this by taking the + # id of self modulo 2**31 - 1. If this is already in the dictionary, we + # just keep incrementing by one until we find a free space. + with _connection_ref_lock: + handle = id(self) % 2147483647 + while handle in _connection_refs: + handle = (handle + 1) % 2147483647 + _connection_refs[handle] = self + + result = Security.SSLSetConnection(self.context, handle) + _assert_no_error(result) + + # If we have a server hostname, we should set that too. + if server_hostname: + if not isinstance(server_hostname, bytes): + server_hostname = server_hostname.encode("utf-8") + + result = Security.SSLSetPeerDomainName( + self.context, server_hostname, len(server_hostname) + ) + _assert_no_error(result) + + # Setup the ciphers. + self._set_ciphers() + + # Set the minimum and maximum TLS versions. + result = Security.SSLSetProtocolVersionMin(self.context, min_version) + _assert_no_error(result) + + result = Security.SSLSetProtocolVersionMax(self.context, max_version) + _assert_no_error(result) + + # If there's a trust DB, we need to use it. We do that by telling + # SecureTransport to break on server auth. We also do that if we don't + # want to validate the certs at all: we just won't actually do any + # authing in that case. + if not verify or trust_bundle is not None: + result = Security.SSLSetSessionOption( + self.context, SecurityConst.kSSLSessionOptionBreakOnServerAuth, True + ) + _assert_no_error(result) + + # If there's a client cert, we need to use it. + if client_cert: + self._keychain, self._keychain_dir = _temporary_keychain() + self._client_cert_chain = _load_client_cert_chain( + self._keychain, client_cert, client_key + ) + result = Security.SSLSetCertificate(self.context, self._client_cert_chain) + _assert_no_error(result) + + while True: + with self._raise_on_error(): + result = Security.SSLHandshake(self.context) + + if result == SecurityConst.errSSLWouldBlock: + raise socket.timeout("handshake timed out") + elif result == SecurityConst.errSSLServerAuthCompleted: + self._custom_validate(verify, trust_bundle) + continue + else: + _assert_no_error(result) + break + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, bufsiz): + buffer = ctypes.create_string_buffer(bufsiz) + bytes_read = self.recv_into(buffer, bufsiz) + data = buffer[:bytes_read] + return data + + def recv_into(self, buffer, nbytes=None): + # Read short on EOF. + if self._closed: + return 0 + + if nbytes is None: + nbytes = len(buffer) + + buffer = (ctypes.c_char * nbytes).from_buffer(buffer) + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLRead( + self.context, buffer, nbytes, ctypes.byref(processed_bytes) + ) + + # There are some result codes that we want to treat as "not always + # errors". Specifically, those are errSSLWouldBlock, + # errSSLClosedGraceful, and errSSLClosedNoNotify. + if result == SecurityConst.errSSLWouldBlock: + # If we didn't process any bytes, then this was just a time out. + # However, we can get errSSLWouldBlock in situations when we *did* + # read some data, and in those cases we should just read "short" + # and return. + if processed_bytes.value == 0: + # Timed out, no data read. + raise socket.timeout("recv timed out") + elif result in ( + SecurityConst.errSSLClosedGraceful, + SecurityConst.errSSLClosedNoNotify, + ): + # The remote peer has closed this connection. We should do so as + # well. Note that we don't actually return here because in + # principle this could actually be fired along with return data. + # It's unlikely though. + self.close() + else: + _assert_no_error(result) + + # Ok, we read and probably succeeded. We should return whatever data + # was actually read. + return processed_bytes.value + + def settimeout(self, timeout): + self._timeout = timeout + + def gettimeout(self): + return self._timeout + + def send(self, data): + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLWrite( + self.context, data, len(data), ctypes.byref(processed_bytes) + ) + + if result == SecurityConst.errSSLWouldBlock and processed_bytes.value == 0: + # Timed out + raise socket.timeout("send timed out") + else: + _assert_no_error(result) + + # We sent, and probably succeeded. Tell them how much we sent. + return processed_bytes.value + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self.send(data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]) + total_sent += sent + + def shutdown(self): + with self._raise_on_error(): + Security.SSLClose(self.context) + + def close(self): + # TODO: should I do clean shutdown here? Do I have to? + if self._makefile_refs < 1: + self._closed = True + if self.context: + CoreFoundation.CFRelease(self.context) + self.context = None + if self._client_cert_chain: + CoreFoundation.CFRelease(self._client_cert_chain) + self._client_cert_chain = None + if self._keychain: + Security.SecKeychainDelete(self._keychain) + CoreFoundation.CFRelease(self._keychain) + shutil.rmtree(self._keychain_dir) + self._keychain = self._keychain_dir = None + return self.socket.close() + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + # Urgh, annoying. + # + # Here's how we do this: + # + # 1. Call SSLCopyPeerTrust to get hold of the trust object for this + # connection. + # 2. Call SecTrustGetCertificateAtIndex for index 0 to get the leaf. + # 3. To get the CN, call SecCertificateCopyCommonName and process that + # string so that it's of the appropriate type. + # 4. To get the SAN, we need to do something a bit more complex: + # a. Call SecCertificateCopyValues to get the data, requesting + # kSecOIDSubjectAltName. + # b. Mess about with this dictionary to try to get the SANs out. + # + # This is gross. Really gross. It's going to be a few hundred LoC extra + # just to repeat something that SecureTransport can *already do*. So my + # operating assumption at this time is that what we want to do is + # instead to just flag to urllib3 that it shouldn't do its own hostname + # validation when using SecureTransport. + if not binary_form: + raise ValueError("SecureTransport only supports dumping binary certs") + trust = Security.SecTrustRef() + certdata = None + der_bytes = None + + try: + # Grab the trust store. + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) + _assert_no_error(result) + if not trust: + # Probably we haven't done the handshake yet. No biggie. + return None + + cert_count = Security.SecTrustGetCertificateCount(trust) + if not cert_count: + # Also a case that might happen if we haven't handshaked. + # Handshook? Handshaken? + return None + + leaf = Security.SecTrustGetCertificateAtIndex(trust, 0) + assert leaf + + # Ok, now we want the DER bytes. + certdata = Security.SecCertificateCopyData(leaf) + assert certdata + + data_length = CoreFoundation.CFDataGetLength(certdata) + data_buffer = CoreFoundation.CFDataGetBytePtr(certdata) + der_bytes = ctypes.string_at(data_buffer, data_length) + finally: + if certdata: + CoreFoundation.CFRelease(certdata) + if trust: + CoreFoundation.CFRelease(trust) + + return der_bytes + + def version(self): + protocol = Security.SSLProtocol() + result = Security.SSLGetNegotiatedProtocolVersion( + self.context, ctypes.byref(protocol) + ) + _assert_no_error(result) + if protocol.value == SecurityConst.kTLSProtocol13: + raise ssl.SSLError("SecureTransport does not support TLS 1.3") + elif protocol.value == SecurityConst.kTLSProtocol12: + return "TLSv1.2" + elif protocol.value == SecurityConst.kTLSProtocol11: + return "TLSv1.1" + elif protocol.value == SecurityConst.kTLSProtocol1: + return "TLSv1" + elif protocol.value == SecurityConst.kSSLProtocol3: + return "SSLv3" + elif protocol.value == SecurityConst.kSSLProtocol2: + return "SSLv2" + else: + raise ssl.SSLError("Unknown TLS version: %r" % protocol) + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) + + +else: # Platform-specific: Python 3 + + def makefile(self, mode="r", buffering=None, *args, **kwargs): + # We disable buffering with SecureTransport because it conflicts with + # the buffering that ST does internally (see issue #1153 for more). + buffering = 0 + return backport_makefile(self, mode, buffering, *args, **kwargs) + + +WrappedSocket.makefile = makefile + + +class SecureTransportContext(object): + """ + I am a wrapper class for the SecureTransport library, to translate the + interface of the standard library ``SSLContext`` object to calls into + SecureTransport. + """ + + def __init__(self, protocol): + self._min_version, self._max_version = _protocol_to_min_max[protocol] + self._options = 0 + self._verify = False + self._trust_bundle = None + self._client_cert = None + self._client_key = None + self._client_key_passphrase = None + + @property + def check_hostname(self): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + return True + + @check_hostname.setter + def check_hostname(self, value): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + pass + + @property + def options(self): + # TODO: Well, crap. + # + # So this is the bit of the code that is the most likely to cause us + # trouble. Essentially we need to enumerate all of the SSL options that + # users might want to use and try to see if we can sensibly translate + # them, or whether we should just ignore them. + return self._options + + @options.setter + def options(self, value): + # TODO: Update in line with above. + self._options = value + + @property + def verify_mode(self): + return ssl.CERT_REQUIRED if self._verify else ssl.CERT_NONE + + @verify_mode.setter + def verify_mode(self, value): + self._verify = True if value == ssl.CERT_REQUIRED else False + + def set_default_verify_paths(self): + # So, this has to do something a bit weird. Specifically, what it does + # is nothing. + # + # This means that, if we had previously had load_verify_locations + # called, this does not undo that. We need to do that because it turns + # out that the rest of the urllib3 code will attempt to load the + # default verify paths if it hasn't been told about any paths, even if + # the context itself was sometime earlier. We resolve that by just + # ignoring it. + pass + + def load_default_certs(self): + return self.set_default_verify_paths() + + def set_ciphers(self, ciphers): + # For now, we just require the default cipher string. + if ciphers != util.ssl_.DEFAULT_CIPHERS: + raise ValueError("SecureTransport doesn't support custom cipher strings") + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + # OK, we only really support cadata and cafile. + if capath is not None: + raise ValueError("SecureTransport does not support cert directories") + + self._trust_bundle = cafile or cadata + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._client_cert = certfile + self._client_key = keyfile + self._client_cert_passphrase = password + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): + # So, what do we do here? Firstly, we assert some properties. This is a + # stripped down shim, so there is some functionality we don't support. + # See PEP 543 for the real deal. + assert not server_side + assert do_handshake_on_connect + assert suppress_ragged_eofs + + # Ok, we're good to go. Now we want to create the wrapped socket object + # and store it in the appropriate place. + wrapped_socket = WrappedSocket(sock) + + # Now we can handshake + wrapped_socket.handshake( + server_hostname, + self._verify, + self._trust_bundle, + self._min_version, + self._max_version, + self._client_cert, + self._client_key, + self._client_key_passphrase, + ) + return wrapped_socket diff --git a/robot/lib/python3.8/site-packages/urllib3/contrib/socks.py b/robot/lib/python3.8/site-packages/urllib3/contrib/socks.py new file mode 100644 index 0000000000000000000000000000000000000000..9e97f7aa98f4ccf380af4b9fc32999d01be8af8c --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/contrib/socks.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +""" +This module contains provisional support for SOCKS proxies from within +urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and +SOCKS5. To enable its functionality, either install PySocks or install this +module with the ``socks`` extra. + +The SOCKS implementation supports the full range of urllib3 features. It also +supports the following SOCKS features: + +- SOCKS4A (``proxy_url='socks4a://...``) +- SOCKS4 (``proxy_url='socks4://...``) +- SOCKS5 with remote DNS (``proxy_url='socks5h://...``) +- SOCKS5 with local DNS (``proxy_url='socks5://...``) +- Usernames and passwords for the SOCKS proxy + + .. note:: + It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in + your ``proxy_url`` to ensure that DNS resolution is done from the remote + server instead of client-side when connecting to a domain name. + +SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5 +supports IPv4, IPv6, and domain names. + +When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url`` +will be sent as the ``userid`` section of the SOCKS request:: + + proxy_url="socks4a://@proxy-host" + +When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion +of the ``proxy_url`` will be sent as the username/password to authenticate +with the proxy:: + + proxy_url="socks5h://:@proxy-host" + +""" +from __future__ import absolute_import + +try: + import socks +except ImportError: + import warnings + from ..exceptions import DependencyWarning + + warnings.warn( + ( + "SOCKS support in urllib3 requires the installation of optional " + "dependencies: specifically, PySocks. For more information, see " + "https://urllib3.readthedocs.io/en/latest/contrib.html#socks-proxies" + ), + DependencyWarning, + ) + raise + +from socket import error as SocketError, timeout as SocketTimeout + +from ..connection import HTTPConnection, HTTPSConnection +from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from ..exceptions import ConnectTimeoutError, NewConnectionError +from ..poolmanager import PoolManager +from ..util.url import parse_url + +try: + import ssl +except ImportError: + ssl = None + + +class SOCKSConnection(HTTPConnection): + """ + A plain-text HTTP connection that connects via a SOCKS proxy. + """ + + def __init__(self, *args, **kwargs): + self._socks_options = kwargs.pop("_socks_options") + super(SOCKSConnection, self).__init__(*args, **kwargs) + + def _new_conn(self): + """ + Establish a new connection via the SOCKS proxy. + """ + extra_kw = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + + try: + conn = socks.create_connection( + (self.host, self.port), + proxy_type=self._socks_options["socks_version"], + proxy_addr=self._socks_options["proxy_host"], + proxy_port=self._socks_options["proxy_port"], + proxy_username=self._socks_options["username"], + proxy_password=self._socks_options["password"], + proxy_rdns=self._socks_options["rdns"], + timeout=self.timeout, + **extra_kw + ) + + except SocketTimeout: + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) + + except socks.ProxyError as e: + # This is fragile as hell, but it seems to be the only way to raise + # useful errors here. + if e.socket_err: + error = e.socket_err + if isinstance(error, SocketTimeout): + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) + else: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % error + ) + else: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) + + except SocketError as e: # Defensive: PySocks should catch all these. + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) + + return conn + + +# We don't need to duplicate the Verified/Unverified distinction from +# urllib3/connection.py here because the HTTPSConnection will already have been +# correctly set to either the Verified or Unverified form by that module. This +# means the SOCKSHTTPSConnection will automatically be the correct type. +class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): + pass + + +class SOCKSHTTPConnectionPool(HTTPConnectionPool): + ConnectionCls = SOCKSConnection + + +class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): + ConnectionCls = SOCKSHTTPSConnection + + +class SOCKSProxyManager(PoolManager): + """ + A version of the urllib3 ProxyManager that routes connections via the + defined SOCKS proxy. + """ + + pool_classes_by_scheme = { + "http": SOCKSHTTPConnectionPool, + "https": SOCKSHTTPSConnectionPool, + } + + def __init__( + self, + proxy_url, + username=None, + password=None, + num_pools=10, + headers=None, + **connection_pool_kw + ): + parsed = parse_url(proxy_url) + + if username is None and password is None and parsed.auth is not None: + split = parsed.auth.split(":") + if len(split) == 2: + username, password = split + if parsed.scheme == "socks5": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = False + elif parsed.scheme == "socks5h": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = True + elif parsed.scheme == "socks4": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = False + elif parsed.scheme == "socks4a": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = True + else: + raise ValueError("Unable to determine SOCKS version from %s" % proxy_url) + + self.proxy_url = proxy_url + + socks_options = { + "socks_version": socks_version, + "proxy_host": parsed.host, + "proxy_port": parsed.port, + "username": username, + "password": password, + "rdns": rdns, + } + connection_pool_kw["_socks_options"] = socks_options + + super(SOCKSProxyManager, self).__init__( + num_pools, headers, **connection_pool_kw + ) + + self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/robot/lib/python3.8/site-packages/urllib3/exceptions.py b/robot/lib/python3.8/site-packages/urllib3/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..a809ae19394d0b375ac84cf7a5a06dcbf91afb63 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/exceptions.py @@ -0,0 +1,255 @@ +from __future__ import absolute_import +from six.moves.http_client import IncompleteRead as httplib_IncompleteRead + +# Base Exceptions + + +class HTTPError(Exception): + "Base exception used by this module." + pass + + +class HTTPWarning(Warning): + "Base warning used by this module." + pass + + +class PoolError(HTTPError): + "Base exception for errors caused within a pool." + + def __init__(self, pool, message): + self.pool = pool + HTTPError.__init__(self, "%s: %s" % (pool, message)) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, None) + + +class RequestError(PoolError): + "Base exception for PoolErrors that have associated URLs." + + def __init__(self, pool, url, message): + self.url = url + PoolError.__init__(self, pool, message) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, self.url, None) + + +class SSLError(HTTPError): + "Raised when SSL certificate fails in an HTTPS connection." + pass + + +class ProxyError(HTTPError): + "Raised when the connection to a proxy fails." + pass + + +class DecodeError(HTTPError): + "Raised when automatic decoding based on Content-Type fails." + pass + + +class ProtocolError(HTTPError): + "Raised when something unexpected happens mid-request/response." + pass + + +#: Renamed to ProtocolError but aliased for backwards compatibility. +ConnectionError = ProtocolError + + +# Leaf Exceptions + + +class MaxRetryError(RequestError): + """Raised when the maximum number of retries is exceeded. + + :param pool: The connection pool + :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` + :param string url: The requested Url + :param exceptions.Exception reason: The underlying error + + """ + + def __init__(self, pool, url, reason=None): + self.reason = reason + + message = "Max retries exceeded with url: %s (Caused by %r)" % (url, reason) + + RequestError.__init__(self, pool, url, message) + + +class HostChangedError(RequestError): + "Raised when an existing pool gets a request for a foreign host." + + def __init__(self, pool, url, retries=3): + message = "Tried to open a foreign host with url: %s" % url + RequestError.__init__(self, pool, url, message) + self.retries = retries + + +class TimeoutStateError(HTTPError): + """ Raised when passing an invalid state to a timeout """ + + pass + + +class TimeoutError(HTTPError): + """ Raised when a socket timeout error occurs. + + Catching this error will catch both :exc:`ReadTimeoutErrors + ` and :exc:`ConnectTimeoutErrors `. + """ + + pass + + +class ReadTimeoutError(TimeoutError, RequestError): + "Raised when a socket timeout occurs while receiving data from a server" + pass + + +# This timeout error does not have a URL attached and needs to inherit from the +# base HTTPError +class ConnectTimeoutError(TimeoutError): + "Raised when a socket timeout occurs while connecting to a server" + pass + + +class NewConnectionError(ConnectTimeoutError, PoolError): + "Raised when we fail to establish a new connection. Usually ECONNREFUSED." + pass + + +class EmptyPoolError(PoolError): + "Raised when a pool runs out of connections and no more are allowed." + pass + + +class ClosedPoolError(PoolError): + "Raised when a request enters a pool after the pool has been closed." + pass + + +class LocationValueError(ValueError, HTTPError): + "Raised when there is something wrong with a given URL input." + pass + + +class LocationParseError(LocationValueError): + "Raised when get_host or similar fails to parse the URL input." + + def __init__(self, location): + message = "Failed to parse: %s" % location + HTTPError.__init__(self, message) + + self.location = location + + +class ResponseError(HTTPError): + "Used as a container for an error reason supplied in a MaxRetryError." + GENERIC_ERROR = "too many error responses" + SPECIFIC_ERROR = "too many {status_code} error responses" + + +class SecurityWarning(HTTPWarning): + "Warned when performing security reducing actions" + pass + + +class SubjectAltNameWarning(SecurityWarning): + "Warned when connecting to a host with a certificate missing a SAN." + pass + + +class InsecureRequestWarning(SecurityWarning): + "Warned when making an unverified HTTPS request." + pass + + +class SystemTimeWarning(SecurityWarning): + "Warned when system time is suspected to be wrong" + pass + + +class InsecurePlatformWarning(SecurityWarning): + "Warned when certain SSL configuration is not available on a platform." + pass + + +class SNIMissingWarning(HTTPWarning): + "Warned when making a HTTPS request without SNI available." + pass + + +class DependencyWarning(HTTPWarning): + """ + Warned when an attempt is made to import a module with missing optional + dependencies. + """ + + pass + + +class ResponseNotChunked(ProtocolError, ValueError): + "Response needs to be chunked in order to read it as chunks." + pass + + +class BodyNotHttplibCompatible(HTTPError): + """ + Body should be httplib.HTTPResponse like (have an fp attribute which + returns raw chunks) for read_chunked(). + """ + + pass + + +class IncompleteRead(HTTPError, httplib_IncompleteRead): + """ + Response length doesn't match expected Content-Length + + Subclass of http_client.IncompleteRead to allow int value + for `partial` to avoid creating large objects on streamed + reads. + """ + + def __init__(self, partial, expected): + super(IncompleteRead, self).__init__(partial, expected) + + def __repr__(self): + return "IncompleteRead(%i bytes read, %i more expected)" % ( + self.partial, + self.expected, + ) + + +class InvalidHeader(HTTPError): + "The header provided was somehow invalid." + pass + + +class ProxySchemeUnknown(AssertionError, ValueError): + "ProxyManager does not support the supplied scheme" + # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. + + def __init__(self, scheme): + message = "Not supported proxy scheme %s" % scheme + super(ProxySchemeUnknown, self).__init__(message) + + +class HeaderParsingError(HTTPError): + "Raised by assert_header_parsing, but we convert it to a log.warning statement." + + def __init__(self, defects, unparsed_data): + message = "%s, unparsed data: %r" % (defects or "Unknown", unparsed_data) + super(HeaderParsingError, self).__init__(message) + + +class UnrewindableBodyError(HTTPError): + "urllib3 encountered an error when trying to rewind a body" + pass diff --git a/robot/lib/python3.8/site-packages/urllib3/fields.py b/robot/lib/python3.8/site-packages/urllib3/fields.py new file mode 100644 index 0000000000000000000000000000000000000000..29f95ec3e5415754fdf1dfa9fbd292a6f53582c0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/fields.py @@ -0,0 +1,273 @@ +from __future__ import absolute_import +import email.utils +import mimetypes +import re + +import six + + +def guess_content_type(filename, default="application/octet-stream"): + """ + Guess the "Content-Type" of a file. + + :param filename: + The filename to guess the "Content-Type" of using :mod:`mimetypes`. + :param default: + If no "Content-Type" can be guessed, default to `default`. + """ + if filename: + return mimetypes.guess_type(filename)[0] or default + return default + + +def format_header_param_rfc2231(name, value): + """ + Helper function to format and quote a single header parameter using the + strategy defined in RFC 2231. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows RFC 2388 Section 4.4. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as ``bytes`` or `str``. + :ret: + An RFC-2231-formatted unicode string. + """ + if isinstance(value, six.binary_type): + value = value.decode("utf-8") + + if not any(ch in value for ch in '"\\\r\n'): + result = u'%s="%s"' % (name, value) + try: + result.encode("ascii") + except (UnicodeEncodeError, UnicodeDecodeError): + pass + else: + return result + + if six.PY2: # Python 2: + value = value.encode("utf-8") + + # encode_rfc2231 accepts an encoded string and returns an ascii-encoded + # string in Python 2 but accepts and returns unicode strings in Python 3 + value = email.utils.encode_rfc2231(value, "utf-8") + value = "%s*=%s" % (name, value) + + if six.PY2: # Python 2: + value = value.decode("utf-8") + + return value + + +_HTML5_REPLACEMENTS = { + u"\u0022": u"%22", + # Replace "\" with "\\". + u"\u005C": u"\u005C\u005C", + u"\u005C": u"\u005C\u005C", +} + +# All control characters from 0x00 to 0x1F *except* 0x1B. +_HTML5_REPLACEMENTS.update( + { + six.unichr(cc): u"%{:02X}".format(cc) + for cc in range(0x00, 0x1F + 1) + if cc not in (0x1B,) + } +) + + +def _replace_multiple(value, needles_and_replacements): + def replacer(match): + return needles_and_replacements[match.group(0)] + + pattern = re.compile( + r"|".join([re.escape(needle) for needle in needles_and_replacements.keys()]) + ) + + result = pattern.sub(replacer, value) + + return result + + +def format_header_param_html5(name, value): + """ + Helper function to format and quote a single header parameter using the + HTML5 strategy. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows the `HTML5 Working Draft + Section 4.10.22.7`_ and matches the behavior of curl and modern browsers. + + .. _HTML5 Working Draft Section 4.10.22.7: + https://w3c.github.io/html/sec-forms.html#multipart-form-data + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as ``bytes`` or `str``. + :ret: + A unicode string, stripped of troublesome characters. + """ + if isinstance(value, six.binary_type): + value = value.decode("utf-8") + + value = _replace_multiple(value, _HTML5_REPLACEMENTS) + + return u'%s="%s"' % (name, value) + + +# For backwards-compatibility. +format_header_param = format_header_param_html5 + + +class RequestField(object): + """ + A data container for request body parameters. + + :param name: + The name of this request field. Must be unicode. + :param data: + The data/value body. + :param filename: + An optional filename of the request field. Must be unicode. + :param headers: + An optional dict-like object of headers to initially use for the field. + :param header_formatter: + An optional callable that is used to encode and format the headers. By + default, this is :func:`format_header_param_html5`. + """ + + def __init__( + self, + name, + data, + filename=None, + headers=None, + header_formatter=format_header_param_html5, + ): + self._name = name + self._filename = filename + self.data = data + self.headers = {} + if headers: + self.headers = dict(headers) + self.header_formatter = header_formatter + + @classmethod + def from_tuples(cls, fieldname, value, header_formatter=format_header_param_html5): + """ + A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. + + Supports constructing :class:`~urllib3.fields.RequestField` from + parameter of key/value strings AND key/filetuple. A filetuple is a + (filename, data, MIME type) tuple where the MIME type is optional. + For example:: + + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + + Field names and filenames must be unicode. + """ + if isinstance(value, tuple): + if len(value) == 3: + filename, data, content_type = value + else: + filename, data = value + content_type = guess_content_type(filename) + else: + filename = None + content_type = None + data = value + + request_param = cls( + fieldname, data, filename=filename, header_formatter=header_formatter + ) + request_param.make_multipart(content_type=content_type) + + return request_param + + def _render_part(self, name, value): + """ + Overridable helper function to format a single header parameter. By + default, this calls ``self.header_formatter``. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as a unicode string. + """ + + return self.header_formatter(name, value) + + def _render_parts(self, header_parts): + """ + Helper function to format and quote a single header. + + Useful for single headers that are composed of multiple items. E.g., + 'Content-Disposition' fields. + + :param header_parts: + A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format + as `k1="v1"; k2="v2"; ...`. + """ + parts = [] + iterable = header_parts + if isinstance(header_parts, dict): + iterable = header_parts.items() + + for name, value in iterable: + if value is not None: + parts.append(self._render_part(name, value)) + + return u"; ".join(parts) + + def render_headers(self): + """ + Renders the headers for this request field. + """ + lines = [] + + sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] + for sort_key in sort_keys: + if self.headers.get(sort_key, False): + lines.append(u"%s: %s" % (sort_key, self.headers[sort_key])) + + for header_name, header_value in self.headers.items(): + if header_name not in sort_keys: + if header_value: + lines.append(u"%s: %s" % (header_name, header_value)) + + lines.append(u"\r\n") + return u"\r\n".join(lines) + + def make_multipart( + self, content_disposition=None, content_type=None, content_location=None + ): + """ + Makes this request field into a multipart request field. + + This method overrides "Content-Disposition", "Content-Type" and + "Content-Location" headers to the request parameter. + + :param content_type: + The 'Content-Type' of the request body. + :param content_location: + The 'Content-Location' of the request body. + + """ + self.headers["Content-Disposition"] = content_disposition or u"form-data" + self.headers["Content-Disposition"] += u"; ".join( + [ + u"", + self._render_parts( + ((u"name", self._name), (u"filename", self._filename)) + ), + ] + ) + self.headers["Content-Type"] = content_type + self.headers["Content-Location"] = content_location diff --git a/robot/lib/python3.8/site-packages/urllib3/filepost.py b/robot/lib/python3.8/site-packages/urllib3/filepost.py new file mode 100644 index 0000000000000000000000000000000000000000..3a4d999d8faf9195d8c35212621f5b5be0e3e48b --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/filepost.py @@ -0,0 +1,98 @@ +from __future__ import absolute_import +import binascii +import codecs +import os + +from io import BytesIO + +import six +from six import b +from .fields import RequestField + +writer = codecs.lookup("utf-8")[3] + + +def choose_boundary(): + """ + Our embarrassingly-simple replacement for mimetools.choose_boundary. + """ + boundary = binascii.hexlify(os.urandom(16)) + if not six.PY2: + boundary = boundary.decode("ascii") + return boundary + + +def iter_field_objects(fields): + """ + Iterate over fields. + + Supports list of (k, v) tuples and dicts, and lists of + :class:`~urllib3.fields.RequestField`. + + """ + if isinstance(fields, dict): + i = six.iteritems(fields) + else: + i = iter(fields) + + for field in i: + if isinstance(field, RequestField): + yield field + else: + yield RequestField.from_tuples(*field) + + +def iter_fields(fields): + """ + .. deprecated:: 1.6 + + Iterate over fields. + + The addition of :class:`~urllib3.fields.RequestField` makes this function + obsolete. Instead, use :func:`iter_field_objects`, which returns + :class:`~urllib3.fields.RequestField` objects. + + Supports list of (k, v) tuples and dicts. + """ + if isinstance(fields, dict): + return ((k, v) for k, v in six.iteritems(fields)) + + return ((k, v) for k, v in fields) + + +def encode_multipart_formdata(fields, boundary=None): + """ + Encode a dictionary of ``fields`` using the multipart/form-data MIME format. + + :param fields: + Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). + + :param boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + body = BytesIO() + if boundary is None: + boundary = choose_boundary() + + for field in iter_field_objects(fields): + body.write(b("--%s\r\n" % (boundary))) + + writer(body).write(field.render_headers()) + data = field.data + + if isinstance(data, int): + data = str(data) # Backwards compatibility + + if isinstance(data, six.text_type): + writer(body).write(data) + else: + body.write(data) + + body.write(b"\r\n") + + body.write(b("--%s--\r\n" % (boundary))) + + content_type = str("multipart/form-data; boundary=%s" % boundary) + + return body.getvalue(), content_type diff --git a/robot/lib/python3.8/site-packages/urllib3/packages/__init__.py b/robot/lib/python3.8/site-packages/urllib3/packages/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ffe70a495e8ef0543e17decca5e3aaee5032ca32 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/packages/__init__.py @@ -0,0 +1,11 @@ +from __future__ import absolute_import + +__all__ = ("ssl_match_hostname",) + +try: + # cPython >= 2.7.9 has ssl features backported from Python3 + from ssl import CertificateError + del CertificateError + import ssl as ssl_match_hostname +except ImportError: + from . import ssl_match_hostname diff --git a/robot/lib/python3.8/site-packages/urllib3/packages/backports/__init__.py b/robot/lib/python3.8/site-packages/urllib3/packages/backports/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/urllib3/packages/backports/makefile.py b/robot/lib/python3.8/site-packages/urllib3/packages/backports/makefile.py new file mode 100644 index 0000000000000000000000000000000000000000..a3156a69c08d635f9969df59cdd9b3d898882c73 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/packages/backports/makefile.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +""" +backports.makefile +~~~~~~~~~~~~~~~~~~ + +Backports the Python 3 ``socket.makefile`` method for use with anything that +wants to create a "fake" socket object. +""" +import io + +from socket import SocketIO + + +def backport_makefile( + self, mode="r", buffering=None, encoding=None, errors=None, newline=None +): + """ + Backport of ``socket.makefile`` from Python 3.5. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = SocketIO(self, rawmode) + self._makefile_refs += 1 + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode + return text diff --git a/robot/lib/python3.8/site-packages/urllib3/packages/ssl_match_hostname/__init__.py b/robot/lib/python3.8/site-packages/urllib3/packages/ssl_match_hostname/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..75b6bb1cf0d4f0925410dbaf04f3b2cabd8ade2f --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/packages/ssl_match_hostname/__init__.py @@ -0,0 +1,19 @@ +import sys + +try: + # Our match_hostname function is the same as 3.5's, so we only want to + # import the match_hostname function if it's at least that good. + if sys.version_info < (3, 5): + raise ImportError("Fallback to vendored code") + + from ssl import CertificateError, match_hostname +except ImportError: + try: + # Backport of the function from a pypi module + from backports.ssl_match_hostname import CertificateError, match_hostname + except ImportError: + # Our vendored copy + from ._implementation import CertificateError, match_hostname + +# Not needed, but documenting what we provide. +__all__ = ("CertificateError", "match_hostname") diff --git a/robot/lib/python3.8/site-packages/urllib3/packages/ssl_match_hostname/_implementation.py b/robot/lib/python3.8/site-packages/urllib3/packages/ssl_match_hostname/_implementation.py new file mode 100644 index 0000000000000000000000000000000000000000..689208d3c63f1318e3a9a084a90e6b4532fa49bb --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/packages/ssl_match_hostname/_implementation.py @@ -0,0 +1,160 @@ +"""The match_hostname() function from Python 3.3.3, essential when using SSL.""" + +# Note: This file is under the PSF license as the code comes from the python +# stdlib. http://docs.python.org/3/license.html + +import re +import sys + +# ipaddress has been backported to 2.6+ in pypi. If it is installed on the +# system, use it to handle IPAddress ServerAltnames (this was added in +# python-3.5) otherwise only do DNS matching. This allows +# backports.ssl_match_hostname to continue to be used in Python 2.7. +try: + import ipaddress +except ImportError: + ipaddress = None + +__version__ = "3.5.0.1" + + +class CertificateError(ValueError): + pass + + +def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + # Ported from python3-syntax: + # leftmost, *remainder = dn.split(r'.') + parts = dn.split(r".") + leftmost = parts[0] + remainder = parts[1:] + + wildcards = leftmost.count("*") + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn) + ) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == "*": + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append("[^.]+") + elif leftmost.startswith("xn--") or hostname.startswith("xn--"): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r"\*", "[^.]*")) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r"\A" + r"\.".join(pats) + r"\Z", re.IGNORECASE) + return pat.match(hostname) + + +def _to_unicode(obj): + if isinstance(obj, str) and sys.version_info < (3,): + obj = unicode(obj, encoding="ascii", errors="strict") + return obj + + +def _ipaddress_match(ipname, host_ip): + """Exact matching of IP addresses. + + RFC 6125 explicitly doesn't define an algorithm for this + (section 1.7.2 - "Out of Scope"). + """ + # OpenSSL may add a trailing newline to a subjectAltName's IP address + # Divergence from upstream: ipaddress can't handle byte str + ip = ipaddress.ip_address(_to_unicode(ipname).rstrip()) + return ip == host_ip + + +def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError( + "empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED" + ) + try: + # Divergence from upstream: ipaddress can't handle byte str + host_ip = ipaddress.ip_address(_to_unicode(hostname)) + except ValueError: + # Not an IP address (common case) + host_ip = None + except UnicodeError: + # Divergence from upstream: Have to deal with ipaddress not taking + # byte strings. addresses should be all ascii, so we consider it not + # an ipaddress in this case + host_ip = None + except AttributeError: + # Divergence from upstream: Make ipaddress library optional + if ipaddress is None: + host_ip = None + else: + raise + dnsnames = [] + san = cert.get("subjectAltName", ()) + for key, value in san: + if key == "DNS": + if host_ip is None and _dnsname_match(value, hostname): + return + dnsnames.append(value) + elif key == "IP Address": + if host_ip is not None and _ipaddress_match(value, host_ip): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get("subject", ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == "commonName": + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError( + "hostname %r " + "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames))) + ) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0])) + else: + raise CertificateError( + "no appropriate commonName or subjectAltName fields were found" + ) diff --git a/robot/lib/python3.8/site-packages/urllib3/poolmanager.py b/robot/lib/python3.8/site-packages/urllib3/poolmanager.py new file mode 100644 index 0000000000000000000000000000000000000000..4d2688b141fa34662d875da503f18116701cf21d --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/poolmanager.py @@ -0,0 +1,470 @@ +from __future__ import absolute_import +import collections +import functools +import logging + +from ._collections import RecentlyUsedContainer +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from .connectionpool import port_by_scheme +from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown +import six +from six.moves.urllib.parse import urljoin +from .request import RequestMethods +from .util.url import parse_url +from .util.retry import Retry + + +__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] + + +log = logging.getLogger(__name__) + +SSL_KEYWORDS = ( + "key_file", + "cert_file", + "cert_reqs", + "ca_certs", + "ssl_version", + "ca_cert_dir", + "ssl_context", + "key_password", +) + +# All known keyword arguments that could be provided to the pool manager, its +# pools, or the underlying connections. This is used to construct a pool key. +_key_fields = ( + "key_scheme", # str + "key_host", # str + "key_port", # int + "key_timeout", # int or float or Timeout + "key_retries", # int or Retry + "key_strict", # bool + "key_block", # bool + "key_source_address", # str + "key_key_file", # str + "key_key_password", # str + "key_cert_file", # str + "key_cert_reqs", # str + "key_ca_certs", # str + "key_ssl_version", # str + "key_ca_cert_dir", # str + "key_ssl_context", # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext + "key_maxsize", # int + "key_headers", # dict + "key__proxy", # parsed proxy url + "key__proxy_headers", # dict + "key_socket_options", # list of (level (int), optname (int), value (int or str)) tuples + "key__socks_options", # dict + "key_assert_hostname", # bool or string + "key_assert_fingerprint", # str + "key_server_hostname", # str +) + +#: The namedtuple class used to construct keys for the connection pool. +#: All custom key schemes should include the fields in this key at a minimum. +PoolKey = collections.namedtuple("PoolKey", _key_fields) + + +def _default_key_normalizer(key_class, request_context): + """ + Create a pool key out of a request context dictionary. + + According to RFC 3986, both the scheme and host are case-insensitive. + Therefore, this function normalizes both before constructing the pool + key for an HTTPS request. If you wish to change this behaviour, provide + alternate callables to ``key_fn_by_scheme``. + + :param key_class: + The class to use when constructing the key. This should be a namedtuple + with the ``scheme`` and ``host`` keys at a minimum. + :type key_class: namedtuple + :param request_context: + A dictionary-like object that contain the context for a request. + :type request_context: dict + + :return: A namedtuple that can be used as a connection pool key. + :rtype: PoolKey + """ + # Since we mutate the dictionary, make a copy first + context = request_context.copy() + context["scheme"] = context["scheme"].lower() + context["host"] = context["host"].lower() + + # These are both dictionaries and need to be transformed into frozensets + for key in ("headers", "_proxy_headers", "_socks_options"): + if key in context and context[key] is not None: + context[key] = frozenset(context[key].items()) + + # The socket_options key may be a list and needs to be transformed into a + # tuple. + socket_opts = context.get("socket_options") + if socket_opts is not None: + context["socket_options"] = tuple(socket_opts) + + # Map the kwargs to the names in the namedtuple - this is necessary since + # namedtuples can't have fields starting with '_'. + for key in list(context.keys()): + context["key_" + key] = context.pop(key) + + # Default to ``None`` for keys missing from the context + for field in key_class._fields: + if field not in context: + context[field] = None + + return key_class(**context) + + +#: A dictionary that maps a scheme to a callable that creates a pool key. +#: This can be used to alter the way pool keys are constructed, if desired. +#: Each PoolManager makes a copy of this dictionary so they can be configured +#: globally here, or individually on the instance. +key_fn_by_scheme = { + "http": functools.partial(_default_key_normalizer, PoolKey), + "https": functools.partial(_default_key_normalizer, PoolKey), +} + +pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool} + + +class PoolManager(RequestMethods): + """ + Allows for arbitrary requests while transparently keeping track of + necessary connection pools for you. + + :param num_pools: + Number of connection pools to cache before discarding the least + recently used pool. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param \\**connection_pool_kw: + Additional parameters are used to create fresh + :class:`urllib3.connectionpool.ConnectionPool` instances. + + Example:: + + >>> manager = PoolManager(num_pools=2) + >>> r = manager.request('GET', 'http://google.com/') + >>> r = manager.request('GET', 'http://google.com/mail') + >>> r = manager.request('GET', 'http://yahoo.com/') + >>> len(manager.pools) + 2 + + """ + + proxy = None + + def __init__(self, num_pools=10, headers=None, **connection_pool_kw): + RequestMethods.__init__(self, headers) + self.connection_pool_kw = connection_pool_kw + self.pools = RecentlyUsedContainer(num_pools, dispose_func=lambda p: p.close()) + + # Locally set the pool classes and keys so other PoolManagers can + # override them. + self.pool_classes_by_scheme = pool_classes_by_scheme + self.key_fn_by_scheme = key_fn_by_scheme.copy() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.clear() + # Return False to re-raise any potential exceptions + return False + + def _new_pool(self, scheme, host, port, request_context=None): + """ + Create a new :class:`ConnectionPool` based on host, port, scheme, and + any additional pool keyword arguments. + + If ``request_context`` is provided, it is provided as keyword arguments + to the pool class used. This method is used to actually create the + connection pools handed out by :meth:`connection_from_url` and + companion methods. It is intended to be overridden for customization. + """ + pool_cls = self.pool_classes_by_scheme[scheme] + if request_context is None: + request_context = self.connection_pool_kw.copy() + + # Although the context has everything necessary to create the pool, + # this function has historically only used the scheme, host, and port + # in the positional args. When an API change is acceptable these can + # be removed. + for key in ("scheme", "host", "port"): + request_context.pop(key, None) + + if scheme == "http": + for kw in SSL_KEYWORDS: + request_context.pop(kw, None) + + return pool_cls(host, port, **request_context) + + def clear(self): + """ + Empty our store of pools and direct them all to close. + + This will not affect in-flight connections, but they will not be + re-used after completion. + """ + self.pools.clear() + + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): + """ + Get a :class:`ConnectionPool` based on the host, port, and scheme. + + If ``port`` isn't given, it will be derived from the ``scheme`` using + ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is + provided, it is merged with the instance's ``connection_pool_kw`` + variable and used to create the new connection pool, if one is + needed. + """ + + if not host: + raise LocationValueError("No host specified.") + + request_context = self._merge_pool_kwargs(pool_kwargs) + request_context["scheme"] = scheme or "http" + if not port: + port = port_by_scheme.get(request_context["scheme"].lower(), 80) + request_context["port"] = port + request_context["host"] = host + + return self.connection_from_context(request_context) + + def connection_from_context(self, request_context): + """ + Get a :class:`ConnectionPool` based on the request context. + + ``request_context`` must at least contain the ``scheme`` key and its + value must be a key in ``key_fn_by_scheme`` instance variable. + """ + scheme = request_context["scheme"].lower() + pool_key_constructor = self.key_fn_by_scheme[scheme] + pool_key = pool_key_constructor(request_context) + + return self.connection_from_pool_key(pool_key, request_context=request_context) + + def connection_from_pool_key(self, pool_key, request_context=None): + """ + Get a :class:`ConnectionPool` based on the provided pool key. + + ``pool_key`` should be a namedtuple that only contains immutable + objects. At a minimum it must have the ``scheme``, ``host``, and + ``port`` fields. + """ + with self.pools.lock: + # If the scheme, host, or port doesn't match existing open + # connections, open a new ConnectionPool. + pool = self.pools.get(pool_key) + if pool: + return pool + + # Make a fresh ConnectionPool of the desired type + scheme = request_context["scheme"] + host = request_context["host"] + port = request_context["port"] + pool = self._new_pool(scheme, host, port, request_context=request_context) + self.pools[pool_key] = pool + + return pool + + def connection_from_url(self, url, pool_kwargs=None): + """ + Similar to :func:`urllib3.connectionpool.connection_from_url`. + + If ``pool_kwargs`` is not provided and a new pool needs to be + constructed, ``self.connection_pool_kw`` is used to initialize + the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs`` + is provided, it is used instead. Note that if a new pool does not + need to be created for the request, the provided ``pool_kwargs`` are + not used. + """ + u = parse_url(url) + return self.connection_from_host( + u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs + ) + + def _merge_pool_kwargs(self, override): + """ + Merge a dictionary of override values for self.connection_pool_kw. + + This does not modify self.connection_pool_kw and returns a new dict. + Any keys in the override dictionary with a value of ``None`` are + removed from the merged dictionary. + """ + base_pool_kwargs = self.connection_pool_kw.copy() + if override: + for key, value in override.items(): + if value is None: + try: + del base_pool_kwargs[key] + except KeyError: + pass + else: + base_pool_kwargs[key] = value + return base_pool_kwargs + + def urlopen(self, method, url, redirect=True, **kw): + """ + Same as :meth:`urllib3.connectionpool.HTTPConnectionPool.urlopen` + with custom cross-host redirect logic and only sends the request-uri + portion of the ``url``. + + The given ``url`` parameter must be absolute, such that an appropriate + :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. + """ + u = parse_url(url) + conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) + + kw["assert_same_host"] = False + kw["redirect"] = False + + if "headers" not in kw: + kw["headers"] = self.headers.copy() + + if self.proxy is not None and u.scheme == "http": + response = conn.urlopen(method, url, **kw) + else: + response = conn.urlopen(method, u.request_uri, **kw) + + redirect_location = redirect and response.get_redirect_location() + if not redirect_location: + return response + + # Support relative URLs for redirecting. + redirect_location = urljoin(url, redirect_location) + + # RFC 7231, Section 6.4.4 + if response.status == 303: + method = "GET" + + retries = kw.get("retries") + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # conn.is_same_host() which may use socket.gethostbyname() in the future. + if retries.remove_headers_on_redirect and not conn.is_same_host( + redirect_location + ): + headers = list(six.iterkeys(kw["headers"])) + for header in headers: + if header.lower() in retries.remove_headers_on_redirect: + kw["headers"].pop(header, None) + + try: + retries = retries.increment(method, url, response=response, _pool=conn) + except MaxRetryError: + if retries.raise_on_redirect: + raise + return response + + kw["retries"] = retries + kw["redirect"] = redirect + + log.info("Redirecting %s -> %s", url, redirect_location) + return self.urlopen(method, redirect_location, **kw) + + +class ProxyManager(PoolManager): + """ + Behaves just like :class:`PoolManager`, but sends all requests through + the defined proxy, using the CONNECT method for HTTPS URLs. + + :param proxy_url: + The URL of the proxy to be used. + + :param proxy_headers: + A dictionary containing headers that will be sent to the proxy. In case + of HTTP they are being sent with each request, while in the + HTTPS/CONNECT case they are sent only once. Could be used for proxy + authentication. + + Example: + >>> proxy = urllib3.ProxyManager('http://localhost:3128/') + >>> r1 = proxy.request('GET', 'http://google.com/') + >>> r2 = proxy.request('GET', 'http://httpbin.org/') + >>> len(proxy.pools) + 1 + >>> r3 = proxy.request('GET', 'https://httpbin.org/') + >>> r4 = proxy.request('GET', 'https://twitter.com/') + >>> len(proxy.pools) + 3 + + """ + + def __init__( + self, + proxy_url, + num_pools=10, + headers=None, + proxy_headers=None, + **connection_pool_kw + ): + + if isinstance(proxy_url, HTTPConnectionPool): + proxy_url = "%s://%s:%i" % ( + proxy_url.scheme, + proxy_url.host, + proxy_url.port, + ) + proxy = parse_url(proxy_url) + if not proxy.port: + port = port_by_scheme.get(proxy.scheme, 80) + proxy = proxy._replace(port=port) + + if proxy.scheme not in ("http", "https"): + raise ProxySchemeUnknown(proxy.scheme) + + self.proxy = proxy + self.proxy_headers = proxy_headers or {} + + connection_pool_kw["_proxy"] = self.proxy + connection_pool_kw["_proxy_headers"] = self.proxy_headers + + super(ProxyManager, self).__init__(num_pools, headers, **connection_pool_kw) + + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): + if scheme == "https": + return super(ProxyManager, self).connection_from_host( + host, port, scheme, pool_kwargs=pool_kwargs + ) + + return super(ProxyManager, self).connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs + ) + + def _set_proxy_headers(self, url, headers=None): + """ + Sets headers needed by proxies: specifically, the Accept and Host + headers. Only sets headers not provided by the user. + """ + headers_ = {"Accept": "*/*"} + + netloc = parse_url(url).netloc + if netloc: + headers_["Host"] = netloc + + if headers: + headers_.update(headers) + return headers_ + + def urlopen(self, method, url, redirect=True, **kw): + "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." + u = parse_url(url) + + if u.scheme == "http": + # For proxied HTTPS requests, httplib sets the necessary headers + # on the CONNECT to the proxy. For HTTP, we'll definitely + # need to set 'Host' at the very least. + headers = kw.get("headers", self.headers) + kw["headers"] = self._set_proxy_headers(url, headers) + + return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) + + +def proxy_from_url(url, **kw): + return ProxyManager(proxy_url=url, **kw) diff --git a/robot/lib/python3.8/site-packages/urllib3/request.py b/robot/lib/python3.8/site-packages/urllib3/request.py new file mode 100644 index 0000000000000000000000000000000000000000..2698bc0118c38e7a59fb02457fd2f00ddc1c40ee --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/request.py @@ -0,0 +1,171 @@ +from __future__ import absolute_import + +from .filepost import encode_multipart_formdata +from six.moves.urllib.parse import urlencode + + +__all__ = ["RequestMethods"] + + +class RequestMethods(object): + """ + Convenience mixin for classes who implement a :meth:`urlopen` method, such + as :class:`~urllib3.connectionpool.HTTPConnectionPool` and + :class:`~urllib3.poolmanager.PoolManager`. + + Provides behavior for making common types of HTTP request methods and + decides which type of request field encoding to use. + + Specifically, + + :meth:`.request_encode_url` is for sending requests whose fields are + encoded in the URL (such as GET, HEAD, DELETE). + + :meth:`.request_encode_body` is for sending requests whose fields are + encoded in the *body* of the request using multipart or www-form-urlencoded + (such as for POST, PUT, PATCH). + + :meth:`.request` is for making any kind of request, it will look up the + appropriate encoding format and use one of the above two methods to make + the request. + + Initializer parameters: + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + """ + + _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"} + + def __init__(self, headers=None): + self.headers = headers or {} + + def urlopen( + self, + method, + url, + body=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **kw + ): # Abstract + raise NotImplementedError( + "Classes extending RequestMethods must implement " + "their own ``urlopen`` method." + ) + + def request(self, method, url, fields=None, headers=None, **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the appropriate encoding of + ``fields`` based on the ``method`` used. + + This is a convenience method that requires the least amount of manual + effort. It can be used in most situations, while still having the + option to drop down to more specific methods when necessary, such as + :meth:`request_encode_url`, :meth:`request_encode_body`, + or even the lowest level :meth:`urlopen`. + """ + method = method.upper() + + urlopen_kw["request_url"] = url + + if method in self._encode_url_methods: + return self.request_encode_url( + method, url, fields=fields, headers=headers, **urlopen_kw + ) + else: + return self.request_encode_body( + method, url, fields=fields, headers=headers, **urlopen_kw + ) + + def request_encode_url(self, method, url, fields=None, headers=None, **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the url. This is useful for request methods like GET, HEAD, DELETE, etc. + """ + if headers is None: + headers = self.headers + + extra_kw = {"headers": headers} + extra_kw.update(urlopen_kw) + + if fields: + url += "?" + urlencode(fields) + + return self.urlopen(method, url, **extra_kw) + + def request_encode_body( + self, + method, + url, + fields=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **urlopen_kw + ): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the body. This is useful for request methods like POST, PUT, PATCH, etc. + + When ``encode_multipart=True`` (default), then + :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode + the payload with the appropriate content type. Otherwise + :meth:`urllib.urlencode` is used with the + 'application/x-www-form-urlencoded' content type. + + Multipart encoding must be used when posting files, and it's reasonably + safe to use it in other times too. However, it may break request + signing, such as with OAuth. + + Supports an optional ``fields`` parameter of key/value strings AND + key/filetuple. A filetuple is a (filename, data, MIME type) tuple where + the MIME type is optional. For example:: + + fields = { + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), + 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + } + + When uploading a file, providing a filename (the first parameter of the + tuple) is optional but recommended to best mimic behavior of browsers. + + Note that if ``headers`` are supplied, the 'Content-Type' header will + be overwritten because it depends on the dynamic random boundary string + which is used to compose the body of the request. The random boundary + string can be explicitly set with the ``multipart_boundary`` parameter. + """ + if headers is None: + headers = self.headers + + extra_kw = {"headers": {}} + + if fields: + if "body" in urlopen_kw: + raise TypeError( + "request got values for both 'fields' and 'body', can only specify one." + ) + + if encode_multipart: + body, content_type = encode_multipart_formdata( + fields, boundary=multipart_boundary + ) + else: + body, content_type = ( + urlencode(fields), + "application/x-www-form-urlencoded", + ) + + extra_kw["body"] = body + extra_kw["headers"] = {"Content-Type": content_type} + + extra_kw["headers"].update(headers) + extra_kw.update(urlopen_kw) + + return self.urlopen(method, url, **extra_kw) diff --git a/robot/lib/python3.8/site-packages/urllib3/response.py b/robot/lib/python3.8/site-packages/urllib3/response.py new file mode 100644 index 0000000000000000000000000000000000000000..a884e1d61e414d41a6b1db3996d6950d9eb08ad3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/response.py @@ -0,0 +1,809 @@ +from __future__ import absolute_import +from contextlib import contextmanager +import zlib +import io +import logging +from socket import timeout as SocketTimeout +from socket import error as SocketError + +try: + import brotli +except ImportError: + brotli = None + +from ._collections import HTTPHeaderDict +from .exceptions import ( + BodyNotHttplibCompatible, + ProtocolError, + DecodeError, + ReadTimeoutError, + ResponseNotChunked, + IncompleteRead, + InvalidHeader, +) +from six import string_types as basestring, PY3 +from six.moves import http_client as httplib +from .connection import HTTPException, BaseSSLError +from .util.response import is_fp_closed, is_response_to_head + +log = logging.getLogger(__name__) + + +class DeflateDecoder(object): + def __init__(self): + self._first_try = True + self._data = b"" + self._obj = zlib.decompressobj() + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + if not data: + return data + + if not self._first_try: + return self._obj.decompress(data) + + self._data += data + try: + decompressed = self._obj.decompress(data) + if decompressed: + self._first_try = False + self._data = None + return decompressed + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: + return self.decompress(self._data) + finally: + self._data = None + + +class GzipDecoderState(object): + + FIRST_MEMBER = 0 + OTHER_MEMBERS = 1 + SWALLOW_DATA = 2 + + +class GzipDecoder(object): + def __init__(self): + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + ret = bytearray() + if self._state == GzipDecoderState.SWALLOW_DATA or not data: + return bytes(ret) + while True: + try: + ret += self._obj.decompress(data) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise + data = self._obj.unused_data + if not data: + return bytes(ret) + self._state = GzipDecoderState.OTHER_MEMBERS + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + + +if brotli is not None: + + class BrotliDecoder(object): + # Supports both 'brotlipy' and 'Brotli' packages + # since they share an import name. The top branches + # are for 'brotlipy' and bottom branches for 'Brotli' + def __init__(self): + self._obj = brotli.Decompressor() + + def decompress(self, data): + if hasattr(self._obj, "decompress"): + return self._obj.decompress(data) + return self._obj.process(data) + + def flush(self): + if hasattr(self._obj, "flush"): + return self._obj.flush() + return b"" + + +class MultiDecoder(object): + """ + From RFC7231: + If one or more encodings have been applied to a representation, the + sender that applied the encodings MUST generate a Content-Encoding + header field that lists the content codings in the order in which + they were applied. + """ + + def __init__(self, modes): + self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] + + def flush(self): + return self._decoders[0].flush() + + def decompress(self, data): + for d in reversed(self._decoders): + data = d.decompress(data) + return data + + +def _get_decoder(mode): + if "," in mode: + return MultiDecoder(mode) + + if mode == "gzip": + return GzipDecoder() + + if brotli is not None and mode == "br": + return BrotliDecoder() + + return DeflateDecoder() + + +class HTTPResponse(io.IOBase): + """ + HTTP Response container. + + Backwards-compatible to httplib's HTTPResponse but the response ``body`` is + loaded and decoded on-demand when the ``data`` property is accessed. This + class is also compatible with the Python standard library's :mod:`io` + module, and can hence be treated as a readable object in the context of that + framework. + + Extra parameters for behaviour not present in httplib.HTTPResponse: + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param original_response: + When this HTTPResponse wrapper is generated from an httplib.HTTPResponse + object, it's convenient to include the original for debug purposes. It's + otherwise unused. + + :param retries: + The retries contains the last :class:`~urllib3.util.retry.Retry` that + was used during the request. + + :param enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + + CONTENT_DECODERS = ["gzip", "deflate"] + if brotli is not None: + CONTENT_DECODERS += ["br"] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + + def __init__( + self, + body="", + headers=None, + status=0, + version=0, + reason=None, + strict=0, + preload_content=True, + decode_content=True, + original_response=None, + pool=None, + connection=None, + msg=None, + retries=None, + enforce_content_length=False, + request_method=None, + request_url=None, + auto_close=True, + ): + + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) + self.status = status + self.version = version + self.reason = reason + self.strict = strict + self.decode_content = decode_content + self.retries = retries + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close + + self._decoder = None + self._body = None + self._fp = None + self._original_response = original_response + self._fp_bytes_read = 0 + self.msg = msg + self._request_url = request_url + + if body and isinstance(body, (basestring, bytes)): + self._body = body + + self._pool = pool + self._connection = connection + + if hasattr(body, "read"): + self._fp = body + + # Are we using the chunked-style of transfer encoding? + self.chunked = False + self.chunk_left = None + tr_enc = self.headers.get("transfer-encoding", "").lower() + # Don't incur the penalty of creating a list and then discarding it + encodings = (enc.strip() for enc in tr_enc.split(",")) + if "chunked" in encodings: + self.chunked = True + + # Determine length of response + self.length_remaining = self._init_length(request_method) + + # If requested, preload the body. + if preload_content and not self._body: + self._body = self.read(decode_content=decode_content) + + def get_redirect_location(self): + """ + Should we redirect and where to? + + :returns: Truthy redirect location string if we got a redirect status + code and valid location. ``None`` if redirect status and no + location. ``False`` if not a redirect status code. + """ + if self.status in self.REDIRECT_STATUSES: + return self.headers.get("location") + + return False + + def release_conn(self): + if not self._pool or not self._connection: + return + + self._pool._put_conn(self._connection) + self._connection = None + + @property + def data(self): + # For backwords-compat with earlier urllib3 0.4 and earlier. + if self._body: + return self._body + + if self._fp: + return self.read(cache_content=True) + + @property + def connection(self): + return self._connection + + def isclosed(self): + return is_fp_closed(self._fp) + + def tell(self): + """ + Obtain the number of bytes pulled over the wire so far. May differ from + the amount of content returned by :meth:``HTTPResponse.read`` if bytes + are encoded on the wire (e.g, compressed). + """ + return self._fp_bytes_read + + def _init_length(self, request_method): + """ + Set initial length value for Response content if available. + """ + length = self.headers.get("content-length") + + if length is not None: + if self.chunked: + # This Response will fail with an IncompleteRead if it can't be + # received as chunked. This method falls back to attempt reading + # the response before raising an exception. + log.warning( + "Received response with both Content-Length and " + "Transfer-Encoding set. This is expressly forbidden " + "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " + "attempting to process response as Transfer-Encoding: " + "chunked." + ) + return None + + try: + # RFC 7230 section 3.3.2 specifies multiple content lengths can + # be sent in a single Content-Length header + # (e.g. Content-Length: 42, 42). This line ensures the values + # are all valid ints and that as long as the `set` length is 1, + # all values are the same. Otherwise, the header is invalid. + lengths = set([int(val) for val in length.split(",")]) + if len(lengths) > 1: + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % length + ) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + # Convert status to int for comparison + # In some cases, httplib returns a status of "_UNKNOWN" + try: + status = int(self.status) + except ValueError: + status = 0 + + # Check for responses that shouldn't include a body + if status in (204, 304) or 100 <= status < 200 or request_method == "HEAD": + length = 0 + + return length + + def _init_decoder(self): + """ + Set-up the _decoder attribute if necessary. + """ + # Note: content-encoding value should be case-insensitive, per RFC 7230 + # Section 3.2 + content_encoding = self.headers.get("content-encoding", "").lower() + if self._decoder is None: + if content_encoding in self.CONTENT_DECODERS: + self._decoder = _get_decoder(content_encoding) + elif "," in content_encoding: + encodings = [ + e.strip() + for e in content_encoding.split(",") + if e.strip() in self.CONTENT_DECODERS + ] + if len(encodings): + self._decoder = _get_decoder(content_encoding) + + DECODER_ERROR_CLASSES = (IOError, zlib.error) + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + + def _decode(self, data, decode_content, flush_decoder): + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + return data + + try: + if self._decoder: + data = self._decoder.decompress(data) + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( + "Received response with content-encoding: %s, but " + "failed to decode it." % content_encoding, + e, + ) + if flush_decoder: + data += self._flush_decoder() + + return data + + def _flush_decoder(self): + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + buf = self._decoder.decompress(b"") + return buf + self._decoder.flush() + + return b"" + + @contextmanager + def _error_catcher(self): + """ + Catch low-level python exceptions, instead re-raising urllib3 + variants, so that low-level exceptions are not leaked in the + high-level api. + + On exit, release the connection back to the pool. + """ + clean_exit = False + + try: + try: + yield + + except SocketTimeout: + # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but + # there is yet no clean way to get at it from this context. + raise ReadTimeoutError(self._pool, None, "Read timed out.") + + except BaseSSLError as e: + # FIXME: Is there a better way to differentiate between SSLErrors? + if "read operation timed out" not in str(e): # Defensive: + # This shouldn't happen but just in case we're missing an edge + # case, let's avoid swallowing SSL errors. + raise + + raise ReadTimeoutError(self._pool, None, "Read timed out.") + + except (HTTPException, SocketError) as e: + # This includes IncompleteRead. + raise ProtocolError("Connection broken: %r" % e, e) + + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + finally: + # If we didn't terminate cleanly, we need to throw away our + # connection. + if not clean_exit: + # The response may not be closed but we're not going to use it + # anymore so close it now to ensure that the connection is + # released back to the pool. + if self._original_response: + self._original_response.close() + + # Closing the response may not actually be sufficient to close + # everything, so if we have a hold of the connection close that + # too. + if self._connection: + self._connection.close() + + # If we hold the original response but it's closed now, we should + # return the connection back to the pool. + if self._original_response and self._original_response.isclosed(): + self.release_conn() + + def read(self, amt=None, decode_content=None, cache_content=False): + """ + Similar to :meth:`httplib.HTTPResponse.read`, but with two additional + parameters: ``decode_content`` and ``cache_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param cache_content: + If True, will save the returned data such that the same result is + returned despite of the state of the underlying file object. This + is useful if you want the ``.data`` property to continue working + after having ``.read()`` the file object. (Overridden if ``amt`` is + set.) + """ + self._init_decoder() + if decode_content is None: + decode_content = self.decode_content + + if self._fp is None: + return + + flush_decoder = False + fp_closed = getattr(self._fp, "closed", False) + + with self._error_catcher(): + if amt is None: + # cStringIO doesn't like amt=None + data = self._fp.read() if not fp_closed else b"" + flush_decoder = True + else: + cache_content = False + data = self._fp.read(amt) if not fp_closed else b"" + if ( + amt != 0 and not data + ): # Platform-specific: Buggy versions of Python. + # Close the connection when no data is returned + # + # This is redundant to what httplib/http.client _should_ + # already do. However, versions of python released before + # December 15, 2012 (http://bugs.python.org/issue16298) do + # not properly close the connection in all cases. There is + # no harm in redundantly calling close. + self._fp.close() + flush_decoder = True + if self.enforce_content_length and self.length_remaining not in ( + 0, + None, + ): + # This is an edge case that httplib failed to cover due + # to concerns of backward compatibility. We're + # addressing it here to make sure IncompleteRead is + # raised during streaming, so all calls with incorrect + # Content-Length are caught. + raise IncompleteRead(self._fp_bytes_read, self.length_remaining) + + if data: + self._fp_bytes_read += len(data) + if self.length_remaining is not None: + self.length_remaining -= len(data) + + data = self._decode(data, decode_content, flush_decoder) + + if cache_content: + self._body = data + + return data + + def stream(self, amt=2 ** 16, decode_content=None): + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if self.chunked and self.supports_chunked_reads(): + for line in self.read_chunked(amt, decode_content=decode_content): + yield line + else: + while not is_fp_closed(self._fp): + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + + @classmethod + def from_httplib(ResponseCls, r, **response_kw): + """ + Given an :class:`httplib.HTTPResponse` instance ``r``, return a + corresponding :class:`urllib3.response.HTTPResponse` object. + + Remaining parameters are passed to the HTTPResponse constructor, along + with ``original_response=r``. + """ + headers = r.msg + + if not isinstance(headers, HTTPHeaderDict): + if PY3: + headers = HTTPHeaderDict(headers.items()) + else: + # Python 2.7 + headers = HTTPHeaderDict.from_httplib(headers) + + # HTTPResponse objects in Python 3 don't have a .strict attribute + strict = getattr(r, "strict", 0) + resp = ResponseCls( + body=r, + headers=headers, + status=r.status, + version=r.version, + reason=r.reason, + strict=strict, + original_response=r, + **response_kw + ) + return resp + + # Backwards-compatibility methods for httplib.HTTPResponse + def getheaders(self): + return self.headers + + def getheader(self, name, default=None): + return self.headers.get(name, default) + + # Backwards compatibility for http.cookiejar + def info(self): + return self.headers + + # Overrides from io.IOBase + def close(self): + if not self.closed: + self._fp.close() + + if self._connection: + self._connection.close() + + if not self.auto_close: + io.IOBase.close(self) + + @property + def closed(self): + if not self.auto_close: + return io.IOBase.closed.__get__(self) + elif self._fp is None: + return True + elif hasattr(self._fp, "isclosed"): + return self._fp.isclosed() + elif hasattr(self._fp, "closed"): + return self._fp.closed + else: + return True + + def fileno(self): + if self._fp is None: + raise IOError("HTTPResponse has no file to get a fileno from") + elif hasattr(self._fp, "fileno"): + return self._fp.fileno() + else: + raise IOError( + "The file-like object this HTTPResponse is wrapped " + "around has no file descriptor" + ) + + def flush(self): + if ( + self._fp is not None + and hasattr(self._fp, "flush") + and not getattr(self._fp, "closed", False) + ): + return self._fp.flush() + + def readable(self): + # This method is required for `io` module compatibility. + return True + + def readinto(self, b): + # This method is required for `io` module compatibility. + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[: len(temp)] = temp + return len(temp) + + def supports_chunked_reads(self): + """ + Checks if the underlying file-like object looks like a + httplib.HTTPResponse object. We do this by testing for the fp + attribute. If it is present we assume it returns raw chunks as + processed by read_chunked(). + """ + return hasattr(self._fp, "fp") + + def _update_chunk_length(self): + # First, we'll figure out length of a chunk and then + # we'll try to read it from socket. + if self.chunk_left is not None: + return + line = self._fp.fp.readline() + line = line.split(b";", 1)[0] + try: + self.chunk_left = int(line, 16) + except ValueError: + # Invalid chunked protocol response, abort. + self.close() + raise httplib.IncompleteRead(line) + + def _handle_chunk(self, amt): + returned_chunk = None + if amt is None: + chunk = self._fp._safe_read(self.chunk_left) + returned_chunk = chunk + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + elif amt < self.chunk_left: + value = self._fp._safe_read(amt) + self.chunk_left = self.chunk_left - amt + returned_chunk = value + elif amt == self.chunk_left: + value = self._fp._safe_read(amt) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + returned_chunk = value + else: # amt > self.chunk_left + returned_chunk = self._fp._safe_read(self.chunk_left) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + return returned_chunk + + def read_chunked(self, amt=None, decode_content=None): + """ + Similar to :meth:`HTTPResponse.read`, but with an additional + parameter: ``decode_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + self._init_decoder() + # FIXME: Rewrite this method and make it a class with a better structured logic. + if not self.chunked: + raise ResponseNotChunked( + "Response is not chunked. " + "Header 'transfer-encoding: chunked' is missing." + ) + if not self.supports_chunked_reads(): + raise BodyNotHttplibCompatible( + "Body should be httplib.HTTPResponse like. " + "It should have have an fp attribute which returns raw chunks." + ) + + with self._error_catcher(): + # Don't bother reading the body of a HEAD request. + if self._original_response and is_response_to_head(self._original_response): + self._original_response.close() + return + + # If a response is already read and closed + # then return immediately. + if self._fp.fp is None: + return + + while True: + self._update_chunk_length() + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + decoded = self._decode( + chunk, decode_content=decode_content, flush_decoder=False + ) + if decoded: + yield decoded + + if decode_content: + # On CPython and PyPy, we should never need to flush the + # decoder. However, on Jython we *might* need to, so + # lets defensively do it anyway. + decoded = self._flush_decoder() + if decoded: # Platform-specific: Jython. + yield decoded + + # Chunk content ends with \r\n: discard it. + while True: + line = self._fp.fp.readline() + if not line: + # Some sites may not end with '\r\n'. + break + if line == b"\r\n": + break + + # We read everything; close the "file". + if self._original_response: + self._original_response.close() + + def geturl(self): + """ + Returns the URL that was the source of this response. + If the request that generated this response redirected, this method + will return the final redirect location. + """ + if self.retries is not None and len(self.retries.history): + return self.retries.history[-1].redirect_location + else: + return self._request_url + + def __iter__(self): + buffer = [] + for chunk in self.stream(decode_content=True): + if b"\n" in chunk: + chunk = chunk.split(b"\n") + yield b"".join(buffer) + chunk[0] + b"\n" + for x in chunk[1:-1]: + yield x + b"\n" + if chunk[-1]: + buffer = [chunk[-1]] + else: + buffer = [] + else: + buffer.append(chunk) + if buffer: + yield b"".join(buffer) diff --git a/robot/lib/python3.8/site-packages/urllib3/util/__init__.py b/robot/lib/python3.8/site-packages/urllib3/util/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a96c73a9d85187af6923d548a5cc517f1517e205 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/util/__init__.py @@ -0,0 +1,46 @@ +from __future__ import absolute_import + +# For backwards compatibility, provide imports that used to be here. +from .connection import is_connection_dropped +from .request import make_headers +from .response import is_fp_closed +from .ssl_ import ( + SSLContext, + HAS_SNI, + IS_PYOPENSSL, + IS_SECURETRANSPORT, + assert_fingerprint, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, + PROTOCOL_TLS, +) +from .timeout import current_time, Timeout + +from .retry import Retry +from .url import get_host, parse_url, split_first, Url +from .wait import wait_for_read, wait_for_write + +__all__ = ( + "HAS_SNI", + "IS_PYOPENSSL", + "IS_SECURETRANSPORT", + "SSLContext", + "PROTOCOL_TLS", + "Retry", + "Timeout", + "Url", + "assert_fingerprint", + "current_time", + "is_connection_dropped", + "is_fp_closed", + "get_host", + "parse_url", + "make_headers", + "resolve_cert_reqs", + "resolve_ssl_version", + "split_first", + "ssl_wrap_socket", + "wait_for_read", + "wait_for_write", +) diff --git a/robot/lib/python3.8/site-packages/urllib3/util/connection.py b/robot/lib/python3.8/site-packages/urllib3/util/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..86f0a3b00edc11b2a313c9c7b2109d04f02bb5f6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/util/connection.py @@ -0,0 +1,138 @@ +from __future__ import absolute_import +import socket +from .wait import NoWayToWaitForSocketError, wait_for_read +from ..contrib import _appengine_environ + + +def is_connection_dropped(conn): # Platform-specific + """ + Returns True if the connection is dropped and should be closed. + + :param conn: + :class:`httplib.HTTPConnection` object. + + Note: For platforms like AppEngine, this will always return ``False`` to + let the platform handle connection recycling transparently for us. + """ + sock = getattr(conn, "sock", False) + if sock is False: # Platform-specific: AppEngine + return False + if sock is None: # Connection already closed (such as by httplib). + return True + try: + # Returns True if readable, which here means it's been dropped + return wait_for_read(sock, timeout=0.0) + except NoWayToWaitForSocketError: # Platform-specific: AppEngine + return False + + +# This function is copied from socket.py in the Python 2.7 standard +# library test suite. Added to its signature is only `socket_options`. +# One additional modification is that we avoid binding to IPv6 servers +# discovered in DNS if the system doesn't have IPv6 functionality. +def create_connection( + address, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, + socket_options=None, +): + """Connect to *address* and return the socket object. + + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`getdefaulttimeout` + is used. If *source_address* is set it must be a tuple of (host, port) + for the socket to bind as a source address before making the connection. + An host of '' or port 0 tells the OS to use the default. + """ + + host, port = address + if host.startswith("["): + host = host.strip("[]") + err = None + + # Using the value from allowed_gai_family() in the context of getaddrinfo lets + # us select whether to work with IPv4 DNS records, IPv6 records, or both. + # The original create_connection function always returns all records. + family = allowed_gai_family() + + for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + + # If provided, set socket level options before connecting. + _set_socket_options(sock, socket_options) + + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + return sock + + except socket.error as e: + err = e + if sock is not None: + sock.close() + sock = None + + if err is not None: + raise err + + raise socket.error("getaddrinfo returns an empty list") + + +def _set_socket_options(sock, options): + if options is None: + return + + for opt in options: + sock.setsockopt(*opt) + + +def allowed_gai_family(): + """This function is designed to work in the context of + getaddrinfo, where family=socket.AF_UNSPEC is the default and + will perform a DNS search for both IPv6 and IPv4 records.""" + + family = socket.AF_INET + if HAS_IPV6: + family = socket.AF_UNSPEC + return family + + +def _has_ipv6(host): + """ Returns True if the system can bind an IPv6 address. """ + sock = None + has_ipv6 = False + + # App Engine doesn't support IPV6 sockets and actually has a quota on the + # number of sockets that can be used, so just early out here instead of + # creating a socket needlessly. + # See https://github.com/urllib3/urllib3/issues/1446 + if _appengine_environ.is_appengine_sandbox(): + return False + + if socket.has_ipv6: + # has_ipv6 returns true if cPython was compiled with IPv6 support. + # It does not tell us if the system has IPv6 support enabled. To + # determine that we must bind to an IPv6 address. + # https://github.com/urllib3/urllib3/pull/611 + # https://bugs.python.org/issue658327 + try: + sock = socket.socket(socket.AF_INET6) + sock.bind((host, 0)) + has_ipv6 = True + except Exception: + pass + + if sock: + sock.close() + return has_ipv6 + + +HAS_IPV6 = _has_ipv6("::1") diff --git a/robot/lib/python3.8/site-packages/urllib3/util/queue.py b/robot/lib/python3.8/site-packages/urllib3/util/queue.py new file mode 100644 index 0000000000000000000000000000000000000000..bade85678271d0717ba5b6f6320d4fa2da1d3651 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/util/queue.py @@ -0,0 +1,21 @@ +import collections +import six +from six.moves import queue + +if six.PY2: + # Queue is imported for side effects on MS Windows. See issue #229. + import Queue as _unused_module_Queue # noqa: F401 + + +class LifoQueue(queue.Queue): + def _init(self, _): + self.queue = collections.deque() + + def _qsize(self, len=len): + return len(self.queue) + + def _put(self, item): + self.queue.append(item) + + def _get(self): + return self.queue.pop() diff --git a/robot/lib/python3.8/site-packages/urllib3/util/request.py b/robot/lib/python3.8/site-packages/urllib3/util/request.py new file mode 100644 index 0000000000000000000000000000000000000000..56ae1d64f159544c519ecbbc8c47bc320e3317e8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/util/request.py @@ -0,0 +1,135 @@ +from __future__ import absolute_import +from base64 import b64encode + +from six import b, integer_types +from ..exceptions import UnrewindableBodyError + +ACCEPT_ENCODING = "gzip,deflate" +try: + import brotli as _unused_module_brotli # noqa: F401 +except ImportError: + pass +else: + ACCEPT_ENCODING += ",br" + +_FAILEDTELL = object() + + +def make_headers( + keep_alive=None, + accept_encoding=None, + user_agent=None, + basic_auth=None, + proxy_basic_auth=None, + disable_cache=None, +): + """ + Shortcuts for generating request headers. + + :param keep_alive: + If ``True``, adds 'connection: keep-alive' header. + + :param accept_encoding: + Can be a boolean, list, or string. + ``True`` translates to 'gzip,deflate'. + List will get joined by comma. + String will be used as provided. + + :param user_agent: + String representing the user-agent you want, such as + "python-urllib3/0.6" + + :param basic_auth: + Colon-separated username:password string for 'authorization: basic ...' + auth header. + + :param proxy_basic_auth: + Colon-separated username:password string for 'proxy-authorization: basic ...' + auth header. + + :param disable_cache: + If ``True``, adds 'cache-control: no-cache' header. + + Example:: + + >>> make_headers(keep_alive=True, user_agent="Batman/1.0") + {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + >>> make_headers(accept_encoding=True) + {'accept-encoding': 'gzip,deflate'} + """ + headers = {} + if accept_encoding: + if isinstance(accept_encoding, str): + pass + elif isinstance(accept_encoding, list): + accept_encoding = ",".join(accept_encoding) + else: + accept_encoding = ACCEPT_ENCODING + headers["accept-encoding"] = accept_encoding + + if user_agent: + headers["user-agent"] = user_agent + + if keep_alive: + headers["connection"] = "keep-alive" + + if basic_auth: + headers["authorization"] = "Basic " + b64encode(b(basic_auth)).decode("utf-8") + + if proxy_basic_auth: + headers["proxy-authorization"] = "Basic " + b64encode( + b(proxy_basic_auth) + ).decode("utf-8") + + if disable_cache: + headers["cache-control"] = "no-cache" + + return headers + + +def set_file_position(body, pos): + """ + If a position is provided, move file to that point. + Otherwise, we'll attempt to record a position for future use. + """ + if pos is not None: + rewind_body(body, pos) + elif getattr(body, "tell", None) is not None: + try: + pos = body.tell() + except (IOError, OSError): + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body. + pos = _FAILEDTELL + + return pos + + +def rewind_body(body, body_pos): + """ + Attempt to rewind body to a certain position. + Primarily used for request redirects and retries. + + :param body: + File-like object that supports seek. + + :param int pos: + Position to seek to in file. + """ + body_seek = getattr(body, "seek", None) + if body_seek is not None and isinstance(body_pos, integer_types): + try: + body_seek(body_pos) + except (IOError, OSError): + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect/retry." + ) + elif body_pos is _FAILEDTELL: + raise UnrewindableBodyError( + "Unable to record file position for rewinding " + "request body during a redirect/retry." + ) + else: + raise ValueError( + "body_pos must be of type integer, instead it was %s." % type(body_pos) + ) diff --git a/robot/lib/python3.8/site-packages/urllib3/util/response.py b/robot/lib/python3.8/site-packages/urllib3/util/response.py new file mode 100644 index 0000000000000000000000000000000000000000..bf2ab614780c345eadb0003c667b74fb8565c3bf --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/util/response.py @@ -0,0 +1,86 @@ +from __future__ import absolute_import +from six.moves import http_client as httplib + +from ..exceptions import HeaderParsingError + + +def is_fp_closed(obj): + """ + Checks whether a given file-like object is closed. + + :param obj: + The file-like object to check. + """ + + try: + # Check `isclosed()` first, in case Python3 doesn't set `closed`. + # GH Issue #928 + return obj.isclosed() + except AttributeError: + pass + + try: + # Check via the official file-like-object way. + return obj.closed + except AttributeError: + pass + + try: + # Check if the object is a container for another file-like object that + # gets released on exhaustion (e.g. HTTPResponse). + return obj.fp is None + except AttributeError: + pass + + raise ValueError("Unable to determine whether fp is closed.") + + +def assert_header_parsing(headers): + """ + Asserts whether all headers have been successfully parsed. + Extracts encountered errors from the result of parsing headers. + + Only works on Python 3. + + :param headers: Headers to verify. + :type headers: `httplib.HTTPMessage`. + + :raises urllib3.exceptions.HeaderParsingError: + If parsing errors are found. + """ + + # This will fail silently if we pass in the wrong kind of parameter. + # To make debugging easier add an explicit check. + if not isinstance(headers, httplib.HTTPMessage): + raise TypeError("expected httplib.Message, got {0}.".format(type(headers))) + + defects = getattr(headers, "defects", None) + get_payload = getattr(headers, "get_payload", None) + + unparsed_data = None + if get_payload: + # get_payload is actually email.message.Message.get_payload; + # we're only interested in the result if it's not a multipart message + if not headers.is_multipart(): + payload = get_payload() + + if isinstance(payload, (bytes, str)): + unparsed_data = payload + + if defects or unparsed_data: + raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) + + +def is_response_to_head(response): + """ + Checks whether the request of a response has been a HEAD-request. + Handles the quirks of AppEngine. + + :param conn: + :type conn: :class:`httplib.HTTPResponse` + """ + # FIXME: Can we do this somehow without accessing private httplib _method? + method = response._method + if isinstance(method, int): # Platform-specific: Appengine + return method == 3 + return method.upper() == "HEAD" diff --git a/robot/lib/python3.8/site-packages/urllib3/util/retry.py b/robot/lib/python3.8/site-packages/urllib3/util/retry.py new file mode 100644 index 0000000000000000000000000000000000000000..ce61e50d5a82473e51493d5d09b7dbac4dcbae3f --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/util/retry.py @@ -0,0 +1,450 @@ +from __future__ import absolute_import +import time +import logging +from collections import namedtuple +from itertools import takewhile +import email +import re + +from ..exceptions import ( + ConnectTimeoutError, + MaxRetryError, + ProtocolError, + ReadTimeoutError, + ResponseError, + InvalidHeader, +) +import six + + +log = logging.getLogger(__name__) + + +# Data structure for representing the metadata of requests that result in a retry. +RequestHistory = namedtuple( + "RequestHistory", ["method", "url", "error", "status", "redirect_location"] +) + + +class Retry(object): + """ Retry configuration. + + Each retry attempt will create a new Retry object with updated values, so + they can be safely reused. + + Retries can be defined as a default for a pool:: + + retries = Retry(connect=5, read=2, redirect=5) + http = PoolManager(retries=retries) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', retries=Retry(10)) + + Retries can be disabled by passing ``False``:: + + response = http.request('GET', 'http://example.com/', retries=False) + + Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless + retries are disabled, in which case the causing exception will be raised. + + :param int total: + Total number of retries to allow. Takes precedence over other counts. + + Set to ``None`` to remove this constraint and fall back on other + counts. It's a good idea to set this to some sensibly-high value to + account for unexpected edge cases and avoid infinite retry loops. + + Set to ``0`` to fail on the first retry. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int connect: + How many connection-related errors to retry on. + + These are errors raised before the request is sent to the remote server, + which we assume has not triggered the server to process the request. + + Set to ``0`` to fail on the first retry of this type. + + :param int read: + How many times to retry on read errors. + + These errors are raised after the request was sent to the server, so the + request may have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + :param int redirect: + How many redirects to perform. Limit this to avoid infinite redirect + loops. + + A redirect is a HTTP response with a status code 301, 302, 303, 307 or + 308. + + Set to ``0`` to fail on the first retry of this type. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int status: + How many times to retry on bad status codes. + + These are retries made on responses, where status code matches + ``status_forcelist``. + + Set to ``0`` to fail on the first retry of this type. + + :param iterable method_whitelist: + Set of uppercased HTTP method verbs that we should retry on. + + By default, we only retry on methods which are considered to be + idempotent (multiple requests with the same parameters end with the + same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`. + + Set to a ``False`` value to retry on any verb. + + :param iterable status_forcelist: + A set of integer HTTP status codes that we should force a retry on. + A retry is initiated if the request method is in ``method_whitelist`` + and the response status code is in ``status_forcelist``. + + By default, this is disabled with ``None``. + + :param float backoff_factor: + A backoff factor to apply between attempts after the second try + (most errors are resolved immediately by a second try without a + delay). urllib3 will sleep for:: + + {backoff factor} * (2 ** ({number of total retries} - 1)) + + seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep + for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer + than :attr:`Retry.BACKOFF_MAX`. + + By default, backoff is disabled (set to 0). + + :param bool raise_on_redirect: Whether, if the number of redirects is + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + + :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: + whether we should raise an exception, or return a response, + if status falls in ``status_forcelist`` range and retries have + been exhausted. + + :param tuple history: The history of the request encountered during + each call to :meth:`~Retry.increment`. The list is in the order + the requests occurred. Each list item is of class :class:`RequestHistory`. + + :param bool respect_retry_after_header: + Whether to respect Retry-After header on status codes defined as + :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. + + :param iterable remove_headers_on_redirect: + Sequence of headers to remove from the request when a response + indicating a redirect is returned before firing off the redirected + request. + """ + + DEFAULT_METHOD_WHITELIST = frozenset( + ["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"] + ) + + RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) + + DEFAULT_REDIRECT_HEADERS_BLACKLIST = frozenset(["Authorization"]) + + #: Maximum backoff time. + BACKOFF_MAX = 120 + + def __init__( + self, + total=10, + connect=None, + read=None, + redirect=None, + status=None, + method_whitelist=DEFAULT_METHOD_WHITELIST, + status_forcelist=None, + backoff_factor=0, + raise_on_redirect=True, + raise_on_status=True, + history=None, + respect_retry_after_header=True, + remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST, + ): + + self.total = total + self.connect = connect + self.read = read + self.status = status + + if redirect is False or total is False: + redirect = 0 + raise_on_redirect = False + + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.method_whitelist = method_whitelist + self.backoff_factor = backoff_factor + self.raise_on_redirect = raise_on_redirect + self.raise_on_status = raise_on_status + self.history = history or tuple() + self.respect_retry_after_header = respect_retry_after_header + self.remove_headers_on_redirect = frozenset( + [h.lower() for h in remove_headers_on_redirect] + ) + + def new(self, **kw): + params = dict( + total=self.total, + connect=self.connect, + read=self.read, + redirect=self.redirect, + status=self.status, + method_whitelist=self.method_whitelist, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + raise_on_redirect=self.raise_on_redirect, + raise_on_status=self.raise_on_status, + history=self.history, + remove_headers_on_redirect=self.remove_headers_on_redirect, + respect_retry_after_header=self.respect_retry_after_header, + ) + params.update(kw) + return type(self)(**params) + + @classmethod + def from_int(cls, retries, redirect=True, default=None): + """ Backwards-compatibility for the old retries format.""" + if retries is None: + retries = default if default is not None else cls.DEFAULT + + if isinstance(retries, Retry): + return retries + + redirect = bool(redirect) and None + new_retries = cls(retries, redirect=redirect) + log.debug("Converted retries value: %r -> %r", retries, new_retries) + return new_retries + + def get_backoff_time(self): + """ Formula for computing the current backoff + + :rtype: float + """ + # We want to consider only the last consecutive errors sequence (Ignore redirects). + consecutive_errors_len = len( + list( + takewhile(lambda x: x.redirect_location is None, reversed(self.history)) + ) + ) + if consecutive_errors_len <= 1: + return 0 + + backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) + return min(self.BACKOFF_MAX, backoff_value) + + def parse_retry_after(self, retry_after): + # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = int(retry_after) + else: + retry_date_tuple = email.utils.parsedate(retry_after) + if retry_date_tuple is None: + raise InvalidHeader("Invalid Retry-After header: %s" % retry_after) + retry_date = time.mktime(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + def get_retry_after(self, response): + """ Get the value of Retry-After in seconds. """ + + retry_after = response.getheader("Retry-After") + + if retry_after is None: + return None + + return self.parse_retry_after(retry_after) + + def sleep_for_retry(self, response=None): + retry_after = self.get_retry_after(response) + if retry_after: + time.sleep(retry_after) + return True + + return False + + def _sleep_backoff(self): + backoff = self.get_backoff_time() + if backoff <= 0: + return + time.sleep(backoff) + + def sleep(self, response=None): + """ Sleep between retry attempts. + + This method will respect a server's ``Retry-After`` response header + and sleep the duration of the time requested. If that is not present, it + will use an exponential backoff. By default, the backoff factor is 0 and + this method will return immediately. + """ + + if self.respect_retry_after_header and response: + slept = self.sleep_for_retry(response) + if slept: + return + + self._sleep_backoff() + + def _is_connection_error(self, err): + """ Errors when we're fairly sure that the server did not receive the + request, so it should be safe to retry. + """ + return isinstance(err, ConnectTimeoutError) + + def _is_read_error(self, err): + """ Errors that occur after the request has been started, so we should + assume that the server began processing it. + """ + return isinstance(err, (ReadTimeoutError, ProtocolError)) + + def _is_method_retryable(self, method): + """ Checks if a given HTTP method should be retried upon, depending if + it is included on the method whitelist. + """ + if self.method_whitelist and method.upper() not in self.method_whitelist: + return False + + return True + + def is_retry(self, method, status_code, has_retry_after=False): + """ Is this method/status code retryable? (Based on whitelists and control + variables such as the number of total retries to allow, whether to + respect the Retry-After header, whether this header is present, and + whether the returned status code is on the list of status codes to + be retried upon on the presence of the aforementioned header) + """ + if not self._is_method_retryable(method): + return False + + if self.status_forcelist and status_code in self.status_forcelist: + return True + + return ( + self.total + and self.respect_retry_after_header + and has_retry_after + and (status_code in self.RETRY_AFTER_STATUS_CODES) + ) + + def is_exhausted(self): + """ Are we out of retries? """ + retry_counts = (self.total, self.connect, self.read, self.redirect, self.status) + retry_counts = list(filter(None, retry_counts)) + if not retry_counts: + return False + + return min(retry_counts) < 0 + + def increment( + self, + method=None, + url=None, + response=None, + error=None, + _pool=None, + _stacktrace=None, + ): + """ Return a new Retry object with incremented retry counters. + + :param response: A response object, or None, if the server did not + return a response. + :type response: :class:`~urllib3.response.HTTPResponse` + :param Exception error: An error encountered during the request, or + None if the response was received successfully. + + :return: A new ``Retry`` object. + """ + if self.total is False and error: + # Disabled, indicate to re-raise the error. + raise six.reraise(type(error), error, _stacktrace) + + total = self.total + if total is not None: + total -= 1 + + connect = self.connect + read = self.read + redirect = self.redirect + status_count = self.status + cause = "unknown" + status = None + redirect_location = None + + if error and self._is_connection_error(error): + # Connect retry? + if connect is False: + raise six.reraise(type(error), error, _stacktrace) + elif connect is not None: + connect -= 1 + + elif error and self._is_read_error(error): + # Read retry? + if read is False or not self._is_method_retryable(method): + raise six.reraise(type(error), error, _stacktrace) + elif read is not None: + read -= 1 + + elif response and response.get_redirect_location(): + # Redirect retry? + if redirect is not None: + redirect -= 1 + cause = "too many redirects" + redirect_location = response.get_redirect_location() + status = response.status + + else: + # Incrementing because of a server error like a 500 in + # status_forcelist and a the given method is in the whitelist + cause = ResponseError.GENERIC_ERROR + if response and response.status: + if status_count is not None: + status_count -= 1 + cause = ResponseError.SPECIFIC_ERROR.format(status_code=response.status) + status = response.status + + history = self.history + ( + RequestHistory(method, url, error, status, redirect_location), + ) + + new_retry = self.new( + total=total, + connect=connect, + read=read, + redirect=redirect, + status=status_count, + history=history, + ) + + if new_retry.is_exhausted(): + raise MaxRetryError(_pool, url, error or ResponseError(cause)) + + log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) + + return new_retry + + def __repr__(self): + return ( + "{cls.__name__}(total={self.total}, connect={self.connect}, " + "read={self.read}, redirect={self.redirect}, status={self.status})" + ).format(cls=type(self), self=self) + + +# For backwards compatibility (equivalent to pre-v1.9): +Retry.DEFAULT = Retry(3) diff --git a/robot/lib/python3.8/site-packages/urllib3/util/ssl_.py b/robot/lib/python3.8/site-packages/urllib3/util/ssl_.py new file mode 100644 index 0000000000000000000000000000000000000000..f3079a2bd84c1a88bad25b6bcb1abab29a282167 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/util/ssl_.py @@ -0,0 +1,407 @@ +from __future__ import absolute_import +import errno +import warnings +import hmac +import sys + +from binascii import hexlify, unhexlify +from hashlib import md5, sha1, sha256 + +from .url import IPV4_RE, BRACELESS_IPV6_ADDRZ_RE +from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning +import six + + +SSLContext = None +HAS_SNI = False +IS_PYOPENSSL = False +IS_SECURETRANSPORT = False + +# Maps the length of a digest to a possible hash function producing this digest +HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256} + + +def _const_compare_digest_backport(a, b): + """ + Compare two digests of equal length in constant time. + + The digests must be of type str/bytes. + Returns True if the digests match, and False otherwise. + """ + result = abs(len(a) - len(b)) + for l, r in zip(bytearray(a), bytearray(b)): + result |= l ^ r + return result == 0 + + +_const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport) + +try: # Test for SSL features + import ssl + from ssl import wrap_socket, CERT_REQUIRED + from ssl import HAS_SNI # Has SNI? +except ImportError: + pass + +try: # Platform-specific: Python 3.6 + from ssl import PROTOCOL_TLS + + PROTOCOL_SSLv23 = PROTOCOL_TLS +except ImportError: + try: + from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS + + PROTOCOL_SSLv23 = PROTOCOL_TLS + except ImportError: + PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 + + +try: + from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION +except ImportError: + OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 + OP_NO_COMPRESSION = 0x20000 + + +# A secure default. +# Sources for more information on TLS ciphers: +# +# - https://wiki.mozilla.org/Security/Server_Side_TLS +# - https://www.ssllabs.com/projects/best-practices/index.html +# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ +# +# The general intent is: +# - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), +# - prefer ECDHE over DHE for better performance, +# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and +# security, +# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, +# - disable NULL authentication, MD5 MACs, DSS, and other +# insecure ciphers for security reasons. +# - NOTE: TLS 1.3 cipher suites are managed through a different interface +# not exposed by CPython (yet!) and are enabled by default if they're available. +DEFAULT_CIPHERS = ":".join( + [ + "ECDHE+AESGCM", + "ECDHE+CHACHA20", + "DHE+AESGCM", + "DHE+CHACHA20", + "ECDH+AESGCM", + "DH+AESGCM", + "ECDH+AES", + "DH+AES", + "RSA+AESGCM", + "RSA+AES", + "!aNULL", + "!eNULL", + "!MD5", + "!DSS", + ] +) + +try: + from ssl import SSLContext # Modern SSL? +except ImportError: + + class SSLContext(object): # Platform-specific: Python 2 + def __init__(self, protocol_version): + self.protocol = protocol_version + # Use default values from a real SSLContext + self.check_hostname = False + self.verify_mode = ssl.CERT_NONE + self.ca_certs = None + self.options = 0 + self.certfile = None + self.keyfile = None + self.ciphers = None + + def load_cert_chain(self, certfile, keyfile): + self.certfile = certfile + self.keyfile = keyfile + + def load_verify_locations(self, cafile=None, capath=None): + self.ca_certs = cafile + + if capath is not None: + raise SSLError("CA directories not supported in older Pythons") + + def set_ciphers(self, cipher_suite): + self.ciphers = cipher_suite + + def wrap_socket(self, socket, server_hostname=None, server_side=False): + warnings.warn( + "A true SSLContext object is not available. This prevents " + "urllib3 from configuring SSL appropriately and may cause " + "certain SSL connections to fail. You can upgrade to a newer " + "version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" + "#ssl-warnings", + InsecurePlatformWarning, + ) + kwargs = { + "keyfile": self.keyfile, + "certfile": self.certfile, + "ca_certs": self.ca_certs, + "cert_reqs": self.verify_mode, + "ssl_version": self.protocol, + "server_side": server_side, + } + return wrap_socket(socket, ciphers=self.ciphers, **kwargs) + + +def assert_fingerprint(cert, fingerprint): + """ + Checks if given fingerprint matches the supplied certificate. + + :param cert: + Certificate as bytes object. + :param fingerprint: + Fingerprint as string of hexdigits, can be interspersed by colons. + """ + + fingerprint = fingerprint.replace(":", "").lower() + digest_length = len(fingerprint) + hashfunc = HASHFUNC_MAP.get(digest_length) + if not hashfunc: + raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint)) + + # We need encode() here for py32; works on py2 and p33. + fingerprint_bytes = unhexlify(fingerprint.encode()) + + cert_digest = hashfunc(cert).digest() + + if not _const_compare_digest(cert_digest, fingerprint_bytes): + raise SSLError( + 'Fingerprints did not match. Expected "{0}", got "{1}".'.format( + fingerprint, hexlify(cert_digest) + ) + ) + + +def resolve_cert_reqs(candidate): + """ + Resolves the argument to a numeric constant, which can be passed to + the wrap_socket function/method from the ssl module. + Defaults to :data:`ssl.CERT_REQUIRED`. + If given a string it is assumed to be the name of the constant in the + :mod:`ssl` module or its abbreviation. + (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. + If it's neither `None` nor a string we assume it is already the numeric + constant which can directly be passed to wrap_socket. + """ + if candidate is None: + return CERT_REQUIRED + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "CERT_" + candidate) + return res + + return candidate + + +def resolve_ssl_version(candidate): + """ + like resolve_cert_reqs + """ + if candidate is None: + return PROTOCOL_TLS + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "PROTOCOL_" + candidate) + return res + + return candidate + + +def create_urllib3_context( + ssl_version=None, cert_reqs=None, options=None, ciphers=None +): + """All arguments have the same meaning as ``ssl_wrap_socket``. + + By default, this function does a lot of the same work that + ``ssl.create_default_context`` does on Python 3.4+. It: + + - Disables SSLv2, SSLv3, and compression + - Sets a restricted set of server ciphers + + If you wish to enable SSLv3, you can do:: + + from urllib3.util import ssl_ + context = ssl_.create_urllib3_context() + context.options &= ~ssl_.OP_NO_SSLv3 + + You can do the same to enable compression (substituting ``COMPRESSION`` + for ``SSLv3`` in the last line above). + + :param ssl_version: + The desired protocol version to use. This will default to + PROTOCOL_SSLv23 which will negotiate the highest protocol that both + the server and your installation of OpenSSL support. + :param cert_reqs: + Whether to require the certificate verification. This defaults to + ``ssl.CERT_REQUIRED``. + :param options: + Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``. + :param ciphers: + Which cipher suites to allow the server to select. + :returns: + Constructed SSLContext object with specified options + :rtype: SSLContext + """ + context = SSLContext(ssl_version or PROTOCOL_TLS) + + context.set_ciphers(ciphers or DEFAULT_CIPHERS) + + # Setting the default here, as we may have no ssl module on import + cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs + + if options is None: + options = 0 + # SSLv2 is easily broken and is considered harmful and dangerous + options |= OP_NO_SSLv2 + # SSLv3 has several problems and is now dangerous + options |= OP_NO_SSLv3 + # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ + # (issue #309) + options |= OP_NO_COMPRESSION + + context.options |= options + + # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is + # necessary for conditional client cert authentication with TLS 1.3. + # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older + # versions of Python. We only enable on Python 3.7.4+ or if certificate + # verification is enabled to work around Python issue #37428 + # See: https://bugs.python.org/issue37428 + if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr( + context, "post_handshake_auth", None + ) is not None: + context.post_handshake_auth = True + + context.verify_mode = cert_reqs + if ( + getattr(context, "check_hostname", None) is not None + ): # Platform-specific: Python 3.2 + # We do our own verification, including fingerprints and alternative + # hostnames. So disable it here + context.check_hostname = False + return context + + +def ssl_wrap_socket( + sock, + keyfile=None, + certfile=None, + cert_reqs=None, + ca_certs=None, + server_hostname=None, + ssl_version=None, + ciphers=None, + ssl_context=None, + ca_cert_dir=None, + key_password=None, +): + """ + All arguments except for server_hostname, ssl_context, and ca_cert_dir have + the same meaning as they do when using :func:`ssl.wrap_socket`. + + :param server_hostname: + When SNI is supported, the expected hostname of the certificate + :param ssl_context: + A pre-made :class:`SSLContext` object. If none is provided, one will + be created using :func:`create_urllib3_context`. + :param ciphers: + A string of ciphers we wish the client to support. + :param ca_cert_dir: + A directory containing CA certificates in multiple separate files, as + supported by OpenSSL's -CApath flag or the capath argument to + SSLContext.load_verify_locations(). + :param key_password: + Optional password if the keyfile is encrypted. + """ + context = ssl_context + if context is None: + # Note: This branch of code and all the variables in it are no longer + # used by urllib3 itself. We should consider deprecating and removing + # this code. + context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) + + if ca_certs or ca_cert_dir: + try: + context.load_verify_locations(ca_certs, ca_cert_dir) + except IOError as e: # Platform-specific: Python 2.7 + raise SSLError(e) + # Py33 raises FileNotFoundError which subclasses OSError + # These are not equivalent unless we check the errno attribute + except OSError as e: # Platform-specific: Python 3.3 and beyond + if e.errno == errno.ENOENT: + raise SSLError(e) + raise + + elif ssl_context is None and hasattr(context, "load_default_certs"): + # try to load OS default certs; works well on Windows (require Python3.4+) + context.load_default_certs() + + # Attempt to detect if we get the goofy behavior of the + # keyfile being encrypted and OpenSSL asking for the + # passphrase via the terminal and instead error out. + if keyfile and key_password is None and _is_key_file_encrypted(keyfile): + raise SSLError("Client private key is encrypted, password is required") + + if certfile: + if key_password is None: + context.load_cert_chain(certfile, keyfile) + else: + context.load_cert_chain(certfile, keyfile, key_password) + + # If we detect server_hostname is an IP address then the SNI + # extension should not be used according to RFC3546 Section 3.1 + # We shouldn't warn the user if SNI isn't available but we would + # not be using SNI anyways due to IP address for server_hostname. + if ( + server_hostname is not None and not is_ipaddress(server_hostname) + ) or IS_SECURETRANSPORT: + if HAS_SNI and server_hostname is not None: + return context.wrap_socket(sock, server_hostname=server_hostname) + + warnings.warn( + "An HTTPS request has been made, but the SNI (Server Name " + "Indication) extension to TLS is not available on this platform. " + "This may cause the server to present an incorrect TLS " + "certificate, which can cause validation failures. You can upgrade to " + "a newer version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" + "#ssl-warnings", + SNIMissingWarning, + ) + + return context.wrap_socket(sock) + + +def is_ipaddress(hostname): + """Detects whether the hostname given is an IPv4 or IPv6 address. + Also detects IPv6 addresses with Zone IDs. + + :param str hostname: Hostname to examine. + :return: True if the hostname is an IP address, False otherwise. + """ + if not six.PY2 and isinstance(hostname, bytes): + # IDN A-label bytes are ASCII compatible. + hostname = hostname.decode("ascii") + return bool(IPV4_RE.match(hostname) or BRACELESS_IPV6_ADDRZ_RE.match(hostname)) + + +def _is_key_file_encrypted(key_file): + """Detects if a key file is encrypted or not.""" + with open(key_file, "r") as f: + for line in f: + # Look for Proc-Type: 4,ENCRYPTED + if "ENCRYPTED" in line: + return True + + return False diff --git a/robot/lib/python3.8/site-packages/urllib3/util/timeout.py b/robot/lib/python3.8/site-packages/urllib3/util/timeout.py new file mode 100644 index 0000000000000000000000000000000000000000..9883700556ef70bd153ae48021c3d6fb410d4122 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/util/timeout.py @@ -0,0 +1,258 @@ +from __future__ import absolute_import + +# The default socket timeout, used by httplib to indicate that no timeout was +# specified by the user +from socket import _GLOBAL_DEFAULT_TIMEOUT +import time + +from ..exceptions import TimeoutStateError + +# A sentinel value to indicate that no timeout was specified by the user in +# urllib3 +_Default = object() + + +# Use time.monotonic if available. +current_time = getattr(time, "monotonic", time.time) + + +class Timeout(object): + """ Timeout configuration. + + Timeouts can be defined as a default for a pool:: + + timeout = Timeout(connect=2.0, read=7.0) + http = PoolManager(timeout=timeout) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``:: + + no_timeout = Timeout(connect=None, read=None) + response = http.request('GET', 'http://example.com/, timeout=no_timeout) + + + :param total: + This combines the connect and read timeouts into one; the read timeout + will be set to the time leftover from the connect attempt. In the + event that both a connect timeout and a total are specified, or a read + timeout and a total are specified, the shorter timeout will be applied. + + Defaults to None. + + :type total: integer, float, or None + + :param connect: + The maximum amount of time (in seconds) to wait for a connection + attempt to a server to succeed. Omitting the parameter will default the + connect timeout to the system default, probably `the global default + timeout in socket.py + `_. + None will set an infinite timeout for connection attempts. + + :type connect: integer, float, or None + + :param read: + The maximum amount of time (in seconds) to wait between consecutive + read operations for a response from the server. Omitting the parameter + will default the read timeout to the system default, probably `the + global default timeout in socket.py + `_. + None will set an infinite timeout. + + :type read: integer, float, or None + + .. note:: + + Many factors can affect the total amount of time for urllib3 to return + an HTTP response. + + For example, Python's DNS resolver does not obey the timeout specified + on the socket. Other factors that can affect total request time include + high CPU load, high swap, the program running at a low priority level, + or other behaviors. + + In addition, the read and total timeouts only measure the time between + read operations on the socket connecting the client and the server, + not the total amount of time for the request to return a complete + response. For most requests, the timeout is raised because the server + has not sent the first byte in the specified time. This is not always + the case; if a server streams one byte every fifteen seconds, a timeout + of 20 seconds will not trigger, even though the request will take + several minutes to complete. + + If your goal is to cut off any request after a set amount of wall clock + time, consider having a second "watcher" thread to cut off a slow + request. + """ + + #: A sentinel object representing the default timeout value + DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT + + def __init__(self, total=None, connect=_Default, read=_Default): + self._connect = self._validate_timeout(connect, "connect") + self._read = self._validate_timeout(read, "read") + self.total = self._validate_timeout(total, "total") + self._start_connect = None + + def __str__(self): + return "%s(connect=%r, read=%r, total=%r)" % ( + type(self).__name__, + self._connect, + self._read, + self.total, + ) + + @classmethod + def _validate_timeout(cls, value, name): + """ Check that a timeout attribute is valid. + + :param value: The timeout value to validate + :param name: The name of the timeout attribute to validate. This is + used to specify in error messages. + :return: The validated and casted version of the given value. + :raises ValueError: If it is a numeric value less than or equal to + zero, or the type is not an integer, float, or None. + """ + if value is _Default: + return cls.DEFAULT_TIMEOUT + + if value is None or value is cls.DEFAULT_TIMEOUT: + return value + + if isinstance(value, bool): + raise ValueError( + "Timeout cannot be a boolean value. It must " + "be an int, float or None." + ) + try: + float(value) + except (TypeError, ValueError): + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) + + try: + if value <= 0: + raise ValueError( + "Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than or equal to 0." % (name, value) + ) + except TypeError: + # Python 3 + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) + + return value + + @classmethod + def from_float(cls, timeout): + """ Create a new Timeout from a legacy timeout value. + + The timeout value used by httplib.py sets the same timeout on the + connect(), and recv() socket requests. This creates a :class:`Timeout` + object that sets the individual timeouts to the ``timeout`` value + passed to this function. + + :param timeout: The legacy timeout value. + :type timeout: integer, float, sentinel default object, or None + :return: Timeout object + :rtype: :class:`Timeout` + """ + return Timeout(read=timeout, connect=timeout) + + def clone(self): + """ Create a copy of the timeout object + + Timeout properties are stored per-pool but each request needs a fresh + Timeout object to ensure each one has its own start/stop configured. + + :return: a copy of the timeout object + :rtype: :class:`Timeout` + """ + # We can't use copy.deepcopy because that will also create a new object + # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to + # detect the user default. + return Timeout(connect=self._connect, read=self._read, total=self.total) + + def start_connect(self): + """ Start the timeout clock, used during a connect() attempt + + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to start a timer that has been started already. + """ + if self._start_connect is not None: + raise TimeoutStateError("Timeout timer has already been started.") + self._start_connect = current_time() + return self._start_connect + + def get_connect_duration(self): + """ Gets the time elapsed since the call to :meth:`start_connect`. + + :return: Elapsed time in seconds. + :rtype: float + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to get duration for a timer that hasn't been started. + """ + if self._start_connect is None: + raise TimeoutStateError( + "Can't get connect duration for timer that has not started." + ) + return current_time() - self._start_connect + + @property + def connect_timeout(self): + """ Get the value to use when setting a connection timeout. + + This will be a positive float or integer, the value None + (never timeout), or the default system timeout. + + :return: Connect timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + """ + if self.total is None: + return self._connect + + if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: + return self.total + + return min(self._connect, self.total) + + @property + def read_timeout(self): + """ Get the value for the read timeout. + + This assumes some time has elapsed in the connection timeout and + computes the read timeout appropriately. + + If self.total is set, the read timeout is dependent on the amount of + time taken by the connect timeout. If the connection time has not been + established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be + raised. + + :return: Value to use for the read timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` + has not yet been called on this object. + """ + if ( + self.total is not None + and self.total is not self.DEFAULT_TIMEOUT + and self._read is not None + and self._read is not self.DEFAULT_TIMEOUT + ): + # In case the connect timeout has not yet been established. + if self._start_connect is None: + return self._read + return max(0, min(self.total - self.get_connect_duration(), self._read)) + elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: + return max(0, self.total - self.get_connect_duration()) + else: + return self._read diff --git a/robot/lib/python3.8/site-packages/urllib3/util/url.py b/robot/lib/python3.8/site-packages/urllib3/util/url.py new file mode 100644 index 0000000000000000000000000000000000000000..6a8a8f9a35e86e2466acfced607d59b2a616551b --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/util/url.py @@ -0,0 +1,430 @@ +from __future__ import absolute_import +import re +from collections import namedtuple + +from ..exceptions import LocationParseError +import six + + +url_attrs = ["scheme", "auth", "host", "port", "path", "query", "fragment"] + +# We only want to normalize urls with an HTTP(S) scheme. +# urllib3 infers URLs without a scheme (None) to be http. +NORMALIZABLE_SCHEMES = ("http", "https", None) + +# Almost all of these patterns were derived from the +# 'rfc3986' module: https://github.com/python-hyper/rfc3986 +PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") +SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") +URI_RE = re.compile( + r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?" + r"(?://([^/?#]*))?" + r"([^?#]*)" + r"(?:\?([^#]*))?" + r"(?:#(.*))?$", + re.UNICODE | re.DOTALL, +) + +IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" +HEX_PAT = "[0-9A-Fa-f]{1,4}" +LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT) +_subs = {"hex": HEX_PAT, "ls32": LS32_PAT} +_variations = [ + # 6( h16 ":" ) ls32 + "(?:%(hex)s:){6}%(ls32)s", + # "::" 5( h16 ":" ) ls32 + "::(?:%(hex)s:){5}%(ls32)s", + # [ h16 ] "::" 4( h16 ":" ) ls32 + "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s", + # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s", + # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s", + # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s", + # [ *4( h16 ":" ) h16 ] "::" ls32 + "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s", + # [ *5( h16 ":" ) h16 ] "::" h16 + "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s", + # [ *6( h16 ":" ) h16 ] "::" + "(?:(?:%(hex)s:){0,6}%(hex)s)?::", +] + +UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._!\-~" +IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" +ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" +IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]" +REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" +TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") + +IPV4_RE = re.compile("^" + IPV4_PAT + "$") +IPV6_RE = re.compile("^" + IPV6_PAT + "$") +IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$") +BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT[2:-2] + "$") +ZONE_ID_RE = re.compile("(" + ZONE_ID_PAT + r")\]$") + +SUBAUTHORITY_PAT = (u"^(?:(.*)@)?(%s|%s|%s)(?::([0-9]{0,5}))?$") % ( + REG_NAME_PAT, + IPV4_PAT, + IPV6_ADDRZ_PAT, +) +SUBAUTHORITY_RE = re.compile(SUBAUTHORITY_PAT, re.UNICODE | re.DOTALL) + +UNRESERVED_CHARS = set( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~" +) +SUB_DELIM_CHARS = set("!$&'()*+,;=") +USERINFO_CHARS = UNRESERVED_CHARS | SUB_DELIM_CHARS | {":"} +PATH_CHARS = USERINFO_CHARS | {"@", "/"} +QUERY_CHARS = FRAGMENT_CHARS = PATH_CHARS | {"?"} + + +class Url(namedtuple("Url", url_attrs)): + """ + Data structure for representing an HTTP URL. Used as a return value for + :func:`parse_url`. Both the scheme and host are normalized as they are + both case-insensitive according to RFC 3986. + """ + + __slots__ = () + + def __new__( + cls, + scheme=None, + auth=None, + host=None, + port=None, + path=None, + query=None, + fragment=None, + ): + if path and not path.startswith("/"): + path = "/" + path + if scheme is not None: + scheme = scheme.lower() + return super(Url, cls).__new__( + cls, scheme, auth, host, port, path, query, fragment + ) + + @property + def hostname(self): + """For backwards-compatibility with urlparse. We're nice like that.""" + return self.host + + @property + def request_uri(self): + """Absolute path including the query string.""" + uri = self.path or "/" + + if self.query is not None: + uri += "?" + self.query + + return uri + + @property + def netloc(self): + """Network location including host and port""" + if self.port: + return "%s:%d" % (self.host, self.port) + return self.host + + @property + def url(self): + """ + Convert self into a url + + This function should more or less round-trip with :func:`.parse_url`. The + returned url may not be exactly the same as the url inputted to + :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls + with a blank port will have : removed). + + Example: :: + + >>> U = parse_url('http://google.com/mail/') + >>> U.url + 'http://google.com/mail/' + >>> Url('http', 'username:password', 'host.com', 80, + ... '/path', 'query', 'fragment').url + 'http://username:password@host.com:80/path?query#fragment' + """ + scheme, auth, host, port, path, query, fragment = self + url = u"" + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + u"://" + if auth is not None: + url += auth + u"@" + if host is not None: + url += host + if port is not None: + url += u":" + str(port) + if path is not None: + url += path + if query is not None: + url += u"?" + query + if fragment is not None: + url += u"#" + fragment + + return url + + def __str__(self): + return self.url + + +def split_first(s, delims): + """ + .. deprecated:: 1.25 + + Given a string and an iterable of delimiters, split on the first found + delimiter. Return two split parts and the matched delimiter. + + If not found, then the first part is the full input string. + + Example:: + + >>> split_first('foo/bar?baz', '?/=') + ('foo', 'bar?baz', '/') + >>> split_first('foo/bar?baz', '123') + ('foo/bar?baz', '', None) + + Scales linearly with number of delims. Not ideal for large number of delims. + """ + min_idx = None + min_delim = None + for d in delims: + idx = s.find(d) + if idx < 0: + continue + + if min_idx is None or idx < min_idx: + min_idx = idx + min_delim = d + + if min_idx is None or min_idx < 0: + return s, "", None + + return s[:min_idx], s[min_idx + 1 :], min_delim + + +def _encode_invalid_chars(component, allowed_chars, encoding="utf-8"): + """Percent-encodes a URI component without reapplying + onto an already percent-encoded component. + """ + if component is None: + return component + + component = six.ensure_text(component) + + # Normalize existing percent-encoded bytes. + # Try to see if the component we're encoding is already percent-encoded + # so we can skip all '%' characters but still encode all others. + component, percent_encodings = PERCENT_RE.subn( + lambda match: match.group(0).upper(), component + ) + + uri_bytes = component.encode("utf-8", "surrogatepass") + is_percent_encoded = percent_encodings == uri_bytes.count(b"%") + encoded_component = bytearray() + + for i in range(0, len(uri_bytes)): + # Will return a single character bytestring on both Python 2 & 3 + byte = uri_bytes[i : i + 1] + byte_ord = ord(byte) + if (is_percent_encoded and byte == b"%") or ( + byte_ord < 128 and byte.decode() in allowed_chars + ): + encoded_component += byte + continue + encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper())) + + return encoded_component.decode(encoding) + + +def _remove_path_dot_segments(path): + # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code + segments = path.split("/") # Turn the path into a list of segments + output = [] # Initialize the variable to use to store output + + for segment in segments: + # '.' is the current directory, so ignore it, it is superfluous + if segment == ".": + continue + # Anything other than '..', should be appended to the output + elif segment != "..": + output.append(segment) + # In this case segment == '..', if we can, we should pop the last + # element + elif output: + output.pop() + + # If the path starts with '/' and the output is empty or the first string + # is non-empty + if path.startswith("/") and (not output or output[0]): + output.insert(0, "") + + # If the path starts with '/.' or '/..' ensure we add one more empty + # string to add a trailing '/' + if path.endswith(("/.", "/..")): + output.append("") + + return "/".join(output) + + +def _normalize_host(host, scheme): + if host: + if isinstance(host, six.binary_type): + host = six.ensure_str(host) + + if scheme in NORMALIZABLE_SCHEMES: + is_ipv6 = IPV6_ADDRZ_RE.match(host) + if is_ipv6: + match = ZONE_ID_RE.search(host) + if match: + start, end = match.span(1) + zone_id = host[start:end] + + if zone_id.startswith("%25") and zone_id != "%25": + zone_id = zone_id[3:] + else: + zone_id = zone_id[1:] + zone_id = "%" + _encode_invalid_chars(zone_id, UNRESERVED_CHARS) + return host[:start].lower() + zone_id + host[end:] + else: + return host.lower() + elif not IPV4_RE.match(host): + return six.ensure_str( + b".".join([_idna_encode(label) for label in host.split(".")]) + ) + return host + + +def _idna_encode(name): + if name and any([ord(x) > 128 for x in name]): + try: + import idna + except ImportError: + six.raise_from( + LocationParseError("Unable to parse URL without the 'idna' module"), + None, + ) + try: + return idna.encode(name.lower(), strict=True, std3_rules=True) + except idna.IDNAError: + six.raise_from( + LocationParseError(u"Name '%s' is not a valid IDNA label" % name), None + ) + return name.lower().encode("ascii") + + +def _encode_target(target): + """Percent-encodes a request target so that there are no invalid characters""" + path, query = TARGET_RE.match(target).groups() + target = _encode_invalid_chars(path, PATH_CHARS) + query = _encode_invalid_chars(query, QUERY_CHARS) + if query is not None: + target += "?" + query + return target + + +def parse_url(url): + """ + Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is + performed to parse incomplete urls. Fields not provided will be None. + This parser is RFC 3986 compliant. + + The parser logic and helper functions are based heavily on + work done in the ``rfc3986`` module. + + :param str url: URL to parse into a :class:`.Url` namedtuple. + + Partly backwards-compatible with :mod:`urlparse`. + + Example:: + + >>> parse_url('http://google.com/mail/') + Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + >>> parse_url('google.com:80') + Url(scheme=None, host='google.com', port=80, path=None, ...) + >>> parse_url('/foo?bar') + Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + """ + if not url: + # Empty + return Url() + + source_url = url + if not SCHEME_RE.search(url): + url = "//" + url + + try: + scheme, authority, path, query, fragment = URI_RE.match(url).groups() + normalize_uri = scheme is None or scheme.lower() in NORMALIZABLE_SCHEMES + + if scheme: + scheme = scheme.lower() + + if authority: + auth, host, port = SUBAUTHORITY_RE.match(authority).groups() + if auth and normalize_uri: + auth = _encode_invalid_chars(auth, USERINFO_CHARS) + if port == "": + port = None + else: + auth, host, port = None, None, None + + if port is not None: + port = int(port) + if not (0 <= port <= 65535): + raise LocationParseError(url) + + host = _normalize_host(host, scheme) + + if normalize_uri and path: + path = _remove_path_dot_segments(path) + path = _encode_invalid_chars(path, PATH_CHARS) + if normalize_uri and query: + query = _encode_invalid_chars(query, QUERY_CHARS) + if normalize_uri and fragment: + fragment = _encode_invalid_chars(fragment, FRAGMENT_CHARS) + + except (ValueError, AttributeError): + return six.raise_from(LocationParseError(source_url), None) + + # For the sake of backwards compatibility we put empty + # string values for path if there are any defined values + # beyond the path in the URL. + # TODO: Remove this when we break backwards compatibility. + if not path: + if query is not None or fragment is not None: + path = "" + else: + path = None + + # Ensure that each part of the URL is a `str` for + # backwards compatibility. + if isinstance(url, six.text_type): + ensure_func = six.ensure_text + else: + ensure_func = six.ensure_str + + def ensure_type(x): + return x if x is None else ensure_func(x) + + return Url( + scheme=ensure_type(scheme), + auth=ensure_type(auth), + host=ensure_type(host), + port=port, + path=ensure_type(path), + query=ensure_type(query), + fragment=ensure_type(fragment), + ) + + +def get_host(url): + """ + Deprecated. Use :func:`parse_url` instead. + """ + p = parse_url(url) + return p.scheme or "http", p.hostname, p.port diff --git a/robot/lib/python3.8/site-packages/urllib3/util/wait.py b/robot/lib/python3.8/site-packages/urllib3/util/wait.py new file mode 100644 index 0000000000000000000000000000000000000000..d71d2fd722bedb03a45d1f70cdc41d028ffa7d69 --- /dev/null +++ b/robot/lib/python3.8/site-packages/urllib3/util/wait.py @@ -0,0 +1,153 @@ +import errno +from functools import partial +import select +import sys + +try: + from time import monotonic +except ImportError: + from time import time as monotonic + +__all__ = ["NoWayToWaitForSocketError", "wait_for_read", "wait_for_write"] + + +class NoWayToWaitForSocketError(Exception): + pass + + +# How should we wait on sockets? +# +# There are two types of APIs you can use for waiting on sockets: the fancy +# modern stateful APIs like epoll/kqueue, and the older stateless APIs like +# select/poll. The stateful APIs are more efficient when you have a lots of +# sockets to keep track of, because you can set them up once and then use them +# lots of times. But we only ever want to wait on a single socket at a time +# and don't want to keep track of state, so the stateless APIs are actually +# more efficient. So we want to use select() or poll(). +# +# Now, how do we choose between select() and poll()? On traditional Unixes, +# select() has a strange calling convention that makes it slow, or fail +# altogether, for high-numbered file descriptors. The point of poll() is to fix +# that, so on Unixes, we prefer poll(). +# +# On Windows, there is no poll() (or at least Python doesn't provide a wrapper +# for it), but that's OK, because on Windows, select() doesn't have this +# strange calling convention; plain select() works fine. +# +# So: on Windows we use select(), and everywhere else we use poll(). We also +# fall back to select() in case poll() is somehow broken or missing. + +if sys.version_info >= (3, 5): + # Modern Python, that retries syscalls by default + def _retry_on_intr(fn, timeout): + return fn(timeout) + + +else: + # Old and broken Pythons. + def _retry_on_intr(fn, timeout): + if timeout is None: + deadline = float("inf") + else: + deadline = monotonic() + timeout + + while True: + try: + return fn(timeout) + # OSError for 3 <= pyver < 3.5, select.error for pyver <= 2.7 + except (OSError, select.error) as e: + # 'e.args[0]' incantation works for both OSError and select.error + if e.args[0] != errno.EINTR: + raise + else: + timeout = deadline - monotonic() + if timeout < 0: + timeout = 0 + if timeout == float("inf"): + timeout = None + continue + + +def select_wait_for_socket(sock, read=False, write=False, timeout=None): + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + rcheck = [] + wcheck = [] + if read: + rcheck.append(sock) + if write: + wcheck.append(sock) + # When doing a non-blocking connect, most systems signal success by + # marking the socket writable. Windows, though, signals success by marked + # it as "exceptional". We paper over the difference by checking the write + # sockets for both conditions. (The stdlib selectors module does the same + # thing.) + fn = partial(select.select, rcheck, wcheck, wcheck) + rready, wready, xready = _retry_on_intr(fn, timeout) + return bool(rready or wready or xready) + + +def poll_wait_for_socket(sock, read=False, write=False, timeout=None): + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + mask = 0 + if read: + mask |= select.POLLIN + if write: + mask |= select.POLLOUT + poll_obj = select.poll() + poll_obj.register(sock, mask) + + # For some reason, poll() takes timeout in milliseconds + def do_poll(t): + if t is not None: + t *= 1000 + return poll_obj.poll(t) + + return bool(_retry_on_intr(do_poll, timeout)) + + +def null_wait_for_socket(*args, **kwargs): + raise NoWayToWaitForSocketError("no select-equivalent available") + + +def _have_working_poll(): + # Apparently some systems have a select.poll that fails as soon as you try + # to use it, either due to strange configuration or broken monkeypatching + # from libraries like eventlet/greenlet. + try: + poll_obj = select.poll() + _retry_on_intr(poll_obj.poll, 0) + except (AttributeError, OSError): + return False + else: + return True + + +def wait_for_socket(*args, **kwargs): + # We delay choosing which implementation to use until the first time we're + # called. We could do it at import time, but then we might make the wrong + # decision if someone goes wild with monkeypatching select.poll after + # we're imported. + global wait_for_socket + if _have_working_poll(): + wait_for_socket = poll_wait_for_socket + elif hasattr(select, "select"): + wait_for_socket = select_wait_for_socket + else: # Platform-specific: Appengine. + wait_for_socket = null_wait_for_socket + return wait_for_socket(*args, **kwargs) + + +def wait_for_read(sock, timeout=None): + """ Waits for reading to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, read=True, timeout=timeout) + + +def wait_for_write(sock, timeout=None): + """ Waits for writing to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, write=True, timeout=timeout) diff --git a/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/AUTHORS b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/AUTHORS new file mode 100644 index 0000000000000000000000000000000000000000..756cdf49d66759f7fb2fa64b4c5dff674bc9e528 --- /dev/null +++ b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/AUTHORS @@ -0,0 +1,4 @@ +validate_email was created by Syrus Akbary in +April 2012. +This package is based on the work of Noel Bush +https://github.com/noelbush/py_email_validation \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/LICENSE b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..7172e36c66b55bd1c89cc4a99b6c1d3b78506653 --- /dev/null +++ b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/LICENSE @@ -0,0 +1,15 @@ +Validate_email +Copyright (c) 2014, Syrus Akbary, All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3.0 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library diff --git a/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/METADATA b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..a70e664bb96d1d88f1ec84549855767a30d82c60 --- /dev/null +++ b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/METADATA @@ -0,0 +1,66 @@ +Metadata-Version: 2.1 +Name: validate-email +Version: 1.3 +Summary: Validate_email verify if an email address is valid and really exists. +Home-page: http://github.com/syrusakbary/validate_email +Author: Syrus Akbary +Author-email: me@syrusakbary.com +License: LGPL +Download-URL: git@github.com:syrusakbary/validate_email.git +Keywords: email validation verification mx verify +Platform: UNKNOWN + +============== +Validate_email +============== + +Validate_email is a package for Python that check if an email is valid, properly formatted and really exists. + + + +INSTALLATION +============ + +First, you must do:: + + pip install validate_email + +Extra +------ + +For check the domain mx and verify email exits you must have the `pyDNS` package installed:: + + pip install pyDNS + + +USAGE +===== + +Basic usage:: + + from validate_email import validate_email + is_valid = validate_email('example@example.com') + + +Checking domain has SMTP Server +------------------------------- + +Check if the host has SMTP Server:: + + from validate_email import validate_email + is_valid = validate_email('example@example.com',check_mx=True) + + +Verify email exists +------------------- + +Check if the host has SMTP Server and the email really exists:: + + from validate_email import validate_email + is_valid = validate_email('example@example.com',verify=True) + + +TODOs and BUGS +============== +See: http://github.com/syrusakbary/validate_email/issues + diff --git a/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/RECORD b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..935037fb57ea20996fa39c7dedaaa750738147f2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/RECORD @@ -0,0 +1,9 @@ +__pycache__/validate_email.cpython-38.pyc,, +validate_email-1.3.dist-info/AUTHORS,sha256=Tlb2YlG0iGJQWhKvXd_hCQR_8Y5RJnJ_cqs0yd0rfhs,195 +validate_email-1.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +validate_email-1.3.dist-info/LICENSE,sha256=hJRzAIcNolBlvKAOkzCR2-ynAKPi0O8t4u3b2ypwTFo,660 +validate_email-1.3.dist-info/METADATA,sha256=hOj5PolLK4tYv2_Wy9tLMt6ujuZDBf4Z7fHhJ7sxykI,1413 +validate_email-1.3.dist-info/RECORD,, +validate_email-1.3.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92 +validate_email-1.3.dist-info/top_level.txt,sha256=Zme0lynck5_0zqxv8On0zt9V3ZcJAZRdUKHZmBQUIvE,15 +validate_email.py,sha256=lzC4iVZg_tHu3l8c9qg-cttdqH4Nx-HYYbiP0ZR7yFA,8340 diff --git a/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/WHEEL b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..b552003ff90e66227ec90d1b159324f140d46001 --- /dev/null +++ b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..d6e592087d710b826a0b4f0a121cb58da33588b6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/validate_email-1.3.dist-info/top_level.txt @@ -0,0 +1 @@ +validate_email diff --git a/robot/lib/python3.8/site-packages/validate_email.py b/robot/lib/python3.8/site-packages/validate_email.py new file mode 100644 index 0000000000000000000000000000000000000000..3dc50e9255f69fbd0bfb6bf13b796b3399f27c93 --- /dev/null +++ b/robot/lib/python3.8/site-packages/validate_email.py @@ -0,0 +1,212 @@ +# RFC 2822 - style email validation for Python +# (c) 2012 Syrus Akbary +# Extended from (c) 2011 Noel Bush +# for support of mx and user check +# This code is made available to you under the GNU LGPL v3. +# +# This module provides a single method, valid_email_address(), +# which returns True or False to indicate whether a given address +# is valid according to the 'addr-spec' part of the specification +# given in RFC 2822. Ideally, we would like to find this +# in some other library, already thoroughly tested and well- +# maintained. The standard Python library email.utils +# contains a parse_addr() function, but it is not sufficient +# to detect many malformed addresses. +# +# This implementation aims to be faithful to the RFC, with the +# exception of a circular definition (see comments below), and +# with the omission of the pattern components marked as "obsolete". + +import re +import smtplib +import logging +import socket + +try: + raw_input +except NameError: + def raw_input(prompt=''): + return input(prompt) + +try: + import DNS + ServerError = DNS.ServerError + DNS.DiscoverNameServers() +except (ImportError, AttributeError): + DNS = None + + class ServerError(Exception): + pass + +# All we are really doing is comparing the input string to one +# gigantic regular expression. But building that regexp, and +# ensuring its correctness, is made much easier by assembling it +# from the "tokens" defined by the RFC. Each of these tokens is +# tested in the accompanying unit test file. +# +# The section of RFC 2822 from which each pattern component is +# derived is given in an accompanying comment. +# +# (To make things simple, every string below is given as 'raw', +# even when it's not strictly necessary. This way we don't forget +# when it is necessary.) +# +WSP = r'[ \t]' # see 2.2.2. Structured Header Field Bodies +CRLF = r'(?:\r\n)' # see 2.2.3. Long Header Fields +NO_WS_CTL = r'\x01-\x08\x0b\x0c\x0f-\x1f\x7f' # see 3.2.1. Primitive Tokens +QUOTED_PAIR = r'(?:\\.)' # see 3.2.2. Quoted characters +FWS = r'(?:(?:' + WSP + r'*' + CRLF + r')?' + \ + WSP + r'+)' # see 3.2.3. Folding white space and comments +CTEXT = r'[' + NO_WS_CTL + \ + r'\x21-\x27\x2a-\x5b\x5d-\x7e]' # see 3.2.3 +CCONTENT = r'(?:' + CTEXT + r'|' + \ + QUOTED_PAIR + r')' # see 3.2.3 (NB: The RFC includes COMMENT here +# as well, but that would be circular.) +COMMENT = r'\((?:' + FWS + r'?' + CCONTENT + \ + r')*' + FWS + r'?\)' # see 3.2.3 +CFWS = r'(?:' + FWS + r'?' + COMMENT + ')*(?:' + \ + FWS + '?' + COMMENT + '|' + FWS + ')' # see 3.2.3 +ATEXT = r'[\w!#$%&\'\*\+\-/=\?\^`\{\|\}~]' # see 3.2.4. Atom +ATOM = CFWS + r'?' + ATEXT + r'+' + CFWS + r'?' # see 3.2.4 +DOT_ATOM_TEXT = ATEXT + r'+(?:\.' + ATEXT + r'+)*' # see 3.2.4 +DOT_ATOM = CFWS + r'?' + DOT_ATOM_TEXT + CFWS + r'?' # see 3.2.4 +QTEXT = r'[' + NO_WS_CTL + \ + r'\x21\x23-\x5b\x5d-\x7e]' # see 3.2.5. Quoted strings +QCONTENT = r'(?:' + QTEXT + r'|' + \ + QUOTED_PAIR + r')' # see 3.2.5 +QUOTED_STRING = CFWS + r'?' + r'"(?:' + FWS + \ + r'?' + QCONTENT + r')*' + FWS + \ + r'?' + r'"' + CFWS + r'?' +LOCAL_PART = r'(?:' + DOT_ATOM + r'|' + \ + QUOTED_STRING + r')' # see 3.4.1. Addr-spec specification +DTEXT = r'[' + NO_WS_CTL + r'\x21-\x5a\x5e-\x7e]' # see 3.4.1 +DCONTENT = r'(?:' + DTEXT + r'|' + \ + QUOTED_PAIR + r')' # see 3.4.1 +DOMAIN_LITERAL = CFWS + r'?' + r'\[' + \ + r'(?:' + FWS + r'?' + DCONTENT + \ + r')*' + FWS + r'?\]' + CFWS + r'?' # see 3.4.1 +DOMAIN = r'(?:' + DOT_ATOM + r'|' + \ + DOMAIN_LITERAL + r')' # see 3.4.1 +ADDR_SPEC = LOCAL_PART + r'@' + DOMAIN # see 3.4.1 + +# A valid address will match exactly the 3.4.1 addr-spec. +VALID_ADDRESS_REGEXP = '^' + ADDR_SPEC + '$' + +MX_DNS_CACHE = {} +MX_CHECK_CACHE = {} + + +def get_mx_ip(hostname): + if hostname not in MX_DNS_CACHE: + try: + MX_DNS_CACHE[hostname] = DNS.mxlookup(hostname) + except ServerError as e: + if e.rcode == 3: # NXDOMAIN (Non-Existent Domain) + MX_DNS_CACHE[hostname] = None + else: + raise + + return MX_DNS_CACHE[hostname] + + +def validate_email(email, check_mx=False, verify=False, debug=False, smtp_timeout=10): + """Indicate whether the given string is a valid email address + according to the 'addr-spec' portion of RFC 2822 (see section + 3.4.1). Parts of the spec that are marked obsolete are *not* + included in this test, and certain arcane constructions that + depend on circular definitions in the spec may not pass, but in + general this should correctly identify any email address likely + to be in use as of 2011.""" + if debug: + logger = logging.getLogger('validate_email') + logger.setLevel(logging.DEBUG) + else: + logger = None + + try: + assert re.match(VALID_ADDRESS_REGEXP, email) is not None + check_mx |= verify + if check_mx: + if not DNS: + raise Exception('For check the mx records or check if the email exists you must ' + 'have installed pyDNS python package') + hostname = email[email.find('@') + 1:] + mx_hosts = get_mx_ip(hostname) + if mx_hosts is None: + return False + for mx in mx_hosts: + try: + if not verify and mx[1] in MX_CHECK_CACHE: + return MX_CHECK_CACHE[mx[1]] + smtp = smtplib.SMTP(timeout=smtp_timeout) + smtp.connect(mx[1]) + MX_CHECK_CACHE[mx[1]] = True + if not verify: + try: + smtp.quit() + except smtplib.SMTPServerDisconnected: + pass + return True + status, _ = smtp.helo() + if status != 250: + smtp.quit() + if debug: + logger.debug(u'%s answer: %s - %s', mx[1], status, _) + continue + smtp.mail('') + status, _ = smtp.rcpt(email) + if status == 250: + smtp.quit() + return True + if debug: + logger.debug(u'%s answer: %s - %s', mx[1], status, _) + smtp.quit() + except smtplib.SMTPServerDisconnected: # Server not permits verify user + if debug: + logger.debug(u'%s disconected.', mx[1]) + except smtplib.SMTPConnectError: + if debug: + logger.debug(u'Unable to connect to %s.', mx[1]) + return None + except AssertionError: + return False + except (ServerError, socket.error) as e: + if debug: + logger.debug('ServerError or socket.error exception raised (%s).', e) + return None + return True + +if __name__ == "__main__": + import time + while True: + email = raw_input('Enter email for validation: ') + + mx = raw_input('Validate MX record? [yN] ') + if mx.strip().lower() == 'y': + mx = True + else: + mx = False + + validate = raw_input('Try to contact server for address validation? [yN] ') + if validate.strip().lower() == 'y': + validate = True + else: + validate = False + + logging.basicConfig() + + result = validate_email(email, mx, validate, debug=True, smtp_timeout=1) + if result: + print("Valid!") + elif result is None: + print("I'm not sure.") + else: + print("Invalid!") + + time.sleep(1) + + +# import sys + +# sys.modules[__name__],sys.modules['validate_email_module'] = validate_email,sys.modules[__name__] +# from validate_email_module import * diff --git a/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/LICENSE b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..be9700d61a3a0e9f5c6a74f93d187f1805c7acbf --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2020-202x The virtualenv developers + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/METADATA b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..fc4979887aa2ef168dfb65793fc258406f18f08f --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/METADATA @@ -0,0 +1,91 @@ +Metadata-Version: 2.1 +Name: virtualenv +Version: 20.3.0 +Summary: Virtual Python Environment builder +Home-page: https://virtualenv.pypa.io/ +Author: Bernat Gabor +Author-email: gaborjbernat@gmail.com +Maintainer: Bernat Gabor +Maintainer-email: gaborjbernat@gmail.com +License: MIT +Project-URL: Source, https://github.com/pypa/virtualenv +Project-URL: Tracker, https://github.com/pypa/virtualenv/issues +Keywords: virtual,environments,isolated +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Software Development :: Testing +Classifier: Topic :: Utilities +Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7 +Description-Content-Type: text/markdown +Requires-Dist: appdirs (<2,>=1.4.3) +Requires-Dist: distlib (<1,>=0.3.1) +Requires-Dist: filelock (<4,>=3.0.0) +Requires-Dist: six (<2,>=1.9.0) +Requires-Dist: pathlib2 (<3,>=2.3.3) ; python_version < "3.4" and sys_platform != "win32" +Requires-Dist: importlib-resources (>=1.0) ; python_version < "3.7" +Requires-Dist: importlib-metadata (>=0.12) ; python_version < "3.8" +Provides-Extra: docs +Requires-Dist: proselint (>=0.10.2) ; extra == 'docs' +Requires-Dist: sphinx (>=3) ; extra == 'docs' +Requires-Dist: sphinx-argparse (>=0.2.5) ; extra == 'docs' +Requires-Dist: sphinx-rtd-theme (>=0.4.3) ; extra == 'docs' +Requires-Dist: towncrier (>=19.9.0rc1) ; extra == 'docs' +Provides-Extra: testing +Requires-Dist: coverage (>=4) ; extra == 'testing' +Requires-Dist: coverage-enable-subprocess (>=1) ; extra == 'testing' +Requires-Dist: flaky (>=3) ; extra == 'testing' +Requires-Dist: pytest (>=4) ; extra == 'testing' +Requires-Dist: pytest-env (>=0.6.2) ; extra == 'testing' +Requires-Dist: pytest-freezegun (>=0.4.1) ; extra == 'testing' +Requires-Dist: pytest-mock (>=2) ; extra == 'testing' +Requires-Dist: pytest-randomly (>=1) ; extra == 'testing' +Requires-Dist: pytest-timeout (>=1) ; extra == 'testing' +Requires-Dist: packaging (>=20.0) ; (python_version > "3.4") and extra == 'testing' +Requires-Dist: xonsh (>=0.9.16) ; (python_version > "3.4" and python_version != "3.9") and extra == 'testing' + +# virtualenv + +[![PyPI](https://img.shields.io/pypi/v/virtualenv?style=flat-square)](https://pypi.org/project/virtualenv) +[![PyPI - Implementation](https://img.shields.io/pypi/implementation/virtualenv?style=flat-square)](https://pypi.org/project/virtualenv) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/virtualenv?style=flat-square)](https://pypi.org/project/virtualenv) +[![Documentation](https://readthedocs.org/projects/virtualenv/badge/?version=latest&style=flat-square)](http://virtualenv.pypa.io) +[![Gitter Chat](https://img.shields.io/gitter/room/pypa/virtualenv?color=FF004F&style=flat-square)](https://gitter.im/pypa/virtualenv) +[![PyPI - Downloads](https://img.shields.io/pypi/dm/virtualenv?style=flat-square)](https://pypistats.org/packages/virtualenv) +[![PyPI - License](https://img.shields.io/pypi/l/virtualenv?style=flat-square)](https://opensource.org/licenses/MIT) +[![Build Status](https://github.com/pypa/virtualenv/workflows/check/badge.svg?branch=main&event=push)](https://github.com/pypa/virtualenv/actions?query=workflow%3Acheck) +[![codecov](https://codecov.io/gh/pypa/virtualenv/branch/main/graph/badge.svg)](https://codecov.io/gh/pypa/virtualenv) +[![Code style: +black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black) + +A tool for creating isolated `virtual` python environments. + +- [Installation](https://virtualenv.pypa.io/en/latest/installation.html) +- [Documentation](https://virtualenv.pypa.io) +- [Changelog](https://virtualenv.pypa.io/en/latest/changelog.html) +- [Issues](https://github.com/pypa/virtualenv/issues) +- [PyPI](https://pypi.org/project/virtualenv) +- [Github](https://github.com/pypa/virtualenv) + +## Code of Conduct + +Everyone interacting in the virtualenv project's codebases, issue trackers, chat rooms, and mailing lists is expected to +follow the [PSF Code of Conduct](https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md). + + diff --git a/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/RECORD b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..3189c6ebefa382584d55f889f7019570918fc213 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/RECORD @@ -0,0 +1,223 @@ +../../../bin/virtualenv,sha256=TbHGiS3ZFemuuzMPxVUsIhu7NPrgC5PKg0yEHI_BwUk,282 +virtualenv-20.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +virtualenv-20.3.0.dist-info/LICENSE,sha256=XBWRk3jFsqqrexnOpw2M3HX3aHnjJFTkwDmfi3HRcek,1074 +virtualenv-20.3.0.dist-info/METADATA,sha256=sfRacaIE-RcMiwPSQXitMlNHORGUIHOlm2SE-G8Jwlw,4905 +virtualenv-20.3.0.dist-info/RECORD,, +virtualenv-20.3.0.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110 +virtualenv-20.3.0.dist-info/entry_points.txt,sha256=1DALKzYOcffJa7Q15TQlMQu0yeFXEy5W124y0aJEfYU,1615 +virtualenv-20.3.0.dist-info/top_level.txt,sha256=JV-LVlC8YeIw1DgiYI0hEot7tgFy5IWdKVcSG7NyzaI,11 +virtualenv-20.3.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +virtualenv/__init__.py,sha256=SMvpjz4VJ3vJ_yfDDPzJAdi2GJOYd_UBXXuvImO07gk,205 +virtualenv/__main__.py,sha256=QMwDqrR4QbhEivl8yoRmAr6G1BY92gr4n1ConcDIxc4,2770 +virtualenv/__pycache__/__init__.cpython-38.pyc,, +virtualenv/__pycache__/__main__.cpython-38.pyc,, +virtualenv/__pycache__/info.cpython-38.pyc,, +virtualenv/__pycache__/report.cpython-38.pyc,, +virtualenv/__pycache__/version.cpython-38.pyc,, +virtualenv/activation/__init__.py,sha256=jLIERxJXMnHq2fH49RdWqBoaiASres4CTKMdUJOeos0,480 +virtualenv/activation/__pycache__/__init__.cpython-38.pyc,, +virtualenv/activation/__pycache__/activator.cpython-38.pyc,, +virtualenv/activation/__pycache__/via_template.cpython-38.pyc,, +virtualenv/activation/activator.py,sha256=CXomkRvhzcAeygYlDwQdDjfPyZQG85aBab5GIVQPv2M,1341 +virtualenv/activation/bash/__init__.py,sha256=7aC1WfvyzgFrIQs13jOuESuAbuiAnTsKkOe0iReRoaE,312 +virtualenv/activation/bash/__pycache__/__init__.cpython-38.pyc,, +virtualenv/activation/bash/activate.sh,sha256=aHia5vyXg2JwymkvRXCp29Aswcg88Mz5UrssXbX9Jjc,2398 +virtualenv/activation/batch/__init__.py,sha256=K0gVfwuXV7uoaMDL7moWGCq7uTDzI64giZzQQ8s2qnU,733 +virtualenv/activation/batch/__pycache__/__init__.cpython-38.pyc,, +virtualenv/activation/batch/activate.bat,sha256=PeQnWWsjvHT-jIWhYI7hbdzkDBZx5UOstnsCmq5PYtw,1031 +virtualenv/activation/batch/deactivate.bat,sha256=6OznnO-HC2wnWUN7YAT-bj815zeKMXEPC0keyBYwKUU,510 +virtualenv/activation/batch/pydoc.bat,sha256=pVuxn8mn9P_Rd0349fiBEiwIuMvfJQSfgJ2dljUT2fA,24 +virtualenv/activation/cshell/__init__.py,sha256=pw4s5idqQhaEccPxadETEvilBcoxW-UkVQ-RNqPyVCQ,344 +virtualenv/activation/cshell/__pycache__/__init__.cpython-38.pyc,, +virtualenv/activation/cshell/activate.csh,sha256=jYwms8OTiVu9MJwXltuEm43HU09BJUqkrVqyj4sjpDA,1468 +virtualenv/activation/fish/__init__.py,sha256=hDkJq1P1wK2qm6BXydXWA9GMkBpj-TaejbKSceFnGZU,251 +virtualenv/activation/fish/__pycache__/__init__.cpython-38.pyc,, +virtualenv/activation/fish/activate.fish,sha256=V7nVwSI_nsFEMlJjSQxCayNWkjubXi1KSgSw1bEakh8,3099 +virtualenv/activation/powershell/__init__.py,sha256=EA-73s5TUMkgxAhLwucFg3gsBwW5huNh7qB4I7uEU-U,256 +virtualenv/activation/powershell/__pycache__/__init__.cpython-38.pyc,, +virtualenv/activation/powershell/activate.ps1,sha256=jVw_FwfVJzcByQ3Sku-wlnOo_a0-OSpAQ8R17kXVgIM,1807 +virtualenv/activation/python/__init__.py,sha256=Uv53LqOrIT_2dO1FXcUYAnwH1eypG8CJ2InhSx1GRI4,1323 +virtualenv/activation/python/__pycache__/__init__.cpython-38.pyc,, +virtualenv/activation/python/__pycache__/activate_this.cpython-38.pyc,, +virtualenv/activation/python/activate_this.py,sha256=Xpz7exdGSjmWk0KfwHLofIpDPUtazNSNGrxT0-5ZG_s,1208 +virtualenv/activation/via_template.py,sha256=U8LgH-lyTjXIQBUdbd0xOZpXNICpiKhsfpiZwzQg7tU,2372 +virtualenv/activation/xonsh/__init__.py,sha256=7NUevd5EpHRMZdSyR1KgFTe9QQBO94zZOwFH6MR6zjo,355 +virtualenv/activation/xonsh/__pycache__/__init__.cpython-38.pyc,, +virtualenv/activation/xonsh/activate.xsh,sha256=qkKgWfrUjYKrgrmhf45VuBz99EMadtiNU8GMfHZZ7AU,1172 +virtualenv/app_data/__init__.py,sha256=nwgqY-Our_SYcDisLfRLmWrTSPytDkjck9-lzg-pOI8,1462 +virtualenv/app_data/__pycache__/__init__.cpython-38.pyc,, +virtualenv/app_data/__pycache__/base.cpython-38.pyc,, +virtualenv/app_data/__pycache__/na.cpython-38.pyc,, +virtualenv/app_data/__pycache__/read_only.cpython-38.pyc,, +virtualenv/app_data/__pycache__/via_disk_folder.cpython-38.pyc,, +virtualenv/app_data/__pycache__/via_tempdir.cpython-38.pyc,, +virtualenv/app_data/base.py,sha256=dbS5Maob1-Cqs6EVqTmmbjAGeNYA1iw3pmdgYPWCJak,2129 +virtualenv/app_data/na.py,sha256=iMRVpCe4m5Q5WM5bC3ee1wYyfkfHvkcQ-8tgIw4druc,1306 +virtualenv/app_data/read_only.py,sha256=MD-4Bl2SZZiGw0g8qZy0YLBGZGCuFYXnAEvWboF1PSc,1006 +virtualenv/app_data/via_disk_folder.py,sha256=CdNXQkenyH178MtSs2Ve6uDUs30-oZpkOz_1guTtTz0,5597 +virtualenv/app_data/via_tempdir.py,sha256=Z_-PoU7qeZe-idzi3nqys4FX0rfsRgOQ9_7XwX3hxSA,770 +virtualenv/config/__init__.py,sha256=8ArZTco6Meo0W9i4dqnwmDO8BJYTaHX7oQx1o06vCm4,57 +virtualenv/config/__pycache__/__init__.cpython-38.pyc,, +virtualenv/config/__pycache__/convert.cpython-38.pyc,, +virtualenv/config/__pycache__/env_var.cpython-38.pyc,, +virtualenv/config/__pycache__/ini.cpython-38.pyc,, +virtualenv/config/cli/__init__.py,sha256=8ArZTco6Meo0W9i4dqnwmDO8BJYTaHX7oQx1o06vCm4,57 +virtualenv/config/cli/__pycache__/__init__.cpython-38.pyc,, +virtualenv/config/cli/__pycache__/parser.cpython-38.pyc,, +virtualenv/config/cli/parser.py,sha256=y5IqHccLBqFpocpE75X611nVrP8v394VW94a9GAojvE,4524 +virtualenv/config/convert.py,sha256=WYGjMRKVriZkfTH3z1fI0sDQRZxCxAedqWbOGsaquyg,2693 +virtualenv/config/env_var.py,sha256=48XpOurSLLjMX-kXjvOpZuAoOUP-LvnbotTlmebhhFk,844 +virtualenv/config/ini.py,sha256=neMqXrA6IOkLF_M_MCQWQSeqNm4CT8tj_h3GdbJv1Cg,2783 +virtualenv/create/__init__.py,sha256=8ArZTco6Meo0W9i4dqnwmDO8BJYTaHX7oQx1o06vCm4,57 +virtualenv/create/__pycache__/__init__.cpython-38.pyc,, +virtualenv/create/__pycache__/creator.cpython-38.pyc,, +virtualenv/create/__pycache__/debug.cpython-38.pyc,, +virtualenv/create/__pycache__/describe.cpython-38.pyc,, +virtualenv/create/__pycache__/pyenv_cfg.cpython-38.pyc,, +virtualenv/create/creator.py,sha256=4jxxEGXCWd6tInT37QNt-13_yDtcIJdPB6EkoYzDkbM,8889 +virtualenv/create/debug.py,sha256=ETOke8w4Ib8fiufAHVeOkH3v0zrztljw3WjGvZyE0Mk,3342 +virtualenv/create/describe.py,sha256=bm0V2wpFOjdN_MkzZuJAEBSttmi5YGPVwxtwGYU5zQU,3561 +virtualenv/create/pyenv_cfg.py,sha256=VsOGfzUpaVCO3J29zrhIeip4jZ4b7llbe45iOQAIRGg,1717 +virtualenv/create/via_global_ref/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +virtualenv/create/via_global_ref/__pycache__/__init__.cpython-38.pyc,, +virtualenv/create/via_global_ref/__pycache__/_virtualenv.cpython-38.pyc,, +virtualenv/create/via_global_ref/__pycache__/api.cpython-38.pyc,, +virtualenv/create/via_global_ref/__pycache__/store.cpython-38.pyc,, +virtualenv/create/via_global_ref/__pycache__/venv.cpython-38.pyc,, +virtualenv/create/via_global_ref/_virtualenv.py,sha256=aEuMB5MrpKhKwuWumv5J7wTpK6w9jUGR1FXPCdCT5fw,5662 +virtualenv/create/via_global_ref/api.py,sha256=5MPq3XJBuUOBj53oIigeWWPm68M-J_E644WWbz37qOU,4357 +virtualenv/create/via_global_ref/builtin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +virtualenv/create/via_global_ref/builtin/__pycache__/__init__.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/__pycache__/builtin_way.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/__pycache__/ref.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/__pycache__/via_global_self_do.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/builtin_way.py,sha256=hO22nT-itVoYiy8wXrXXYzHw86toCp_Uq-cURR7w6ck,546 +virtualenv/create/via_global_ref/builtin/cpython/__init__.py,sha256=8ArZTco6Meo0W9i4dqnwmDO8BJYTaHX7oQx1o06vCm4,57 +virtualenv/create/via_global_ref/builtin/cpython/__pycache__/__init__.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/cpython/__pycache__/common.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/cpython/__pycache__/cpython2.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/cpython/__pycache__/cpython3.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/cpython/__pycache__/mac_os.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/cpython/common.py,sha256=U7EvB9-2DlOQTGrTyPrEcItEbJ1sFBzo1EAOcAIjQ5Q,2392 +virtualenv/create/via_global_ref/builtin/cpython/cpython2.py,sha256=p41H2g6wAqhJzeUU48nH3u05-yWEbwCzhyj4pn8rnm4,3757 +virtualenv/create/via_global_ref/builtin/cpython/cpython3.py,sha256=MStMTIDn0e1bQLelgioQbfTJP4cfAYacaIB1t2qNg7s,3310 +virtualenv/create/via_global_ref/builtin/cpython/mac_os.py,sha256=B0Lqgo8geZBSKSpHWUB46lDYRggW4Kg2AZUp3Z7xn9M,12382 +virtualenv/create/via_global_ref/builtin/pypy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +virtualenv/create/via_global_ref/builtin/pypy/__pycache__/__init__.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/pypy/__pycache__/common.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/pypy/__pycache__/pypy2.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/pypy/__pycache__/pypy3.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/pypy/common.py,sha256=-t-TZxCTJwpIh_oRsDyv5IilH19jKqJrZa27zWN_8Ws,1816 +virtualenv/create/via_global_ref/builtin/pypy/pypy2.py,sha256=bmMY_KJZ1iD_ifq-X9ZBOlOpJ1aN7839qigBgnWRIdA,3535 +virtualenv/create/via_global_ref/builtin/pypy/pypy3.py,sha256=ti6hmOIC4HiTBnEYKytO-d9wH-eLeMoQxQ0kZRhnNrw,1751 +virtualenv/create/via_global_ref/builtin/python2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +virtualenv/create/via_global_ref/builtin/python2/__pycache__/__init__.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/python2/__pycache__/python2.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/python2/__pycache__/site.cpython-38.pyc,, +virtualenv/create/via_global_ref/builtin/python2/python2.py,sha256=jkJwmkeJVTzwzo95eMIptTfdBA-qmyIqZcpt48iOitU,4276 +virtualenv/create/via_global_ref/builtin/python2/site.py,sha256=4uguJDuWPmB25yBmpsMYKLOnIVXkerck0UO8CP8F2c4,6078 +virtualenv/create/via_global_ref/builtin/ref.py,sha256=xCTICJhE-OiopBxl6ymo1P1NqgK3KEF8ZUOtQDtDTVA,5477 +virtualenv/create/via_global_ref/builtin/via_global_self_do.py,sha256=d569fX7fjq5vHvGGXDjo-1Xi__HhqU2xjDJOuYrmGjw,4552 +virtualenv/create/via_global_ref/store.py,sha256=cqLBEhQ979xHnlidqmxlDjsvj2Wr-mBo7shvGQSEBxU,685 +virtualenv/create/via_global_ref/venv.py,sha256=p5RkDcXhr1pmOwnl1dpS06UYHmfNVy2ld4sTwsYjYWU,2955 +virtualenv/discovery/__init__.py,sha256=8ArZTco6Meo0W9i4dqnwmDO8BJYTaHX7oQx1o06vCm4,57 +virtualenv/discovery/__pycache__/__init__.cpython-38.pyc,, +virtualenv/discovery/__pycache__/builtin.cpython-38.pyc,, +virtualenv/discovery/__pycache__/cached_py_info.cpython-38.pyc,, +virtualenv/discovery/__pycache__/discover.cpython-38.pyc,, +virtualenv/discovery/__pycache__/py_info.cpython-38.pyc,, +virtualenv/discovery/__pycache__/py_spec.cpython-38.pyc,, +virtualenv/discovery/builtin.py,sha256=glPZCnL2yVwa0rS-R5oQ_Ll8fo6OWee1BywWW8FArxg,6195 +virtualenv/discovery/cached_py_info.py,sha256=l2lELE8YkwKXCNopImY2VjmpHPTawh1d3qmdsXMtkRs,5043 +virtualenv/discovery/discover.py,sha256=evJYn4APkwjNmdolNeIBSHiOudkvN59c5oVYI2Zsjlg,1209 +virtualenv/discovery/py_info.py,sha256=QtZFq0xav1tEpKI5seEJaEOkc_FXer21Gzgl_Ccqy98,21793 +virtualenv/discovery/py_spec.py,sha256=wQhLzCfXoSPsAAO9nm5-I2lNolVDux4W2vPSUfJGjlc,4790 +virtualenv/discovery/windows/__init__.py,sha256=wpBh3zR43QbMwRsf5wgcrsavX_jWHTcB6iw18asfrkQ,1185 +virtualenv/discovery/windows/__pycache__/__init__.cpython-38.pyc,, +virtualenv/discovery/windows/__pycache__/pep514.cpython-38.pyc,, +virtualenv/discovery/windows/pep514.py,sha256=YYiaJzo-XuMtO78BMFMAudqkeJiLQkFnUTOuQZ5lJz8,5451 +virtualenv/info.py,sha256=-2pI_kyC9fNj5OR8AQWkKjlpOk4_96Lmbco3atYYBdY,1921 +virtualenv/report.py,sha256=M2OHHCWdOHZsn74tj1MYYKmaI3QRJF8VA1FZIdkQTMQ,1594 +virtualenv/run/__init__.py,sha256=lVIiIq_LoMHUGYkrTSx0tpFG_aYywy_u6GWUReHRcUA,5777 +virtualenv/run/__pycache__/__init__.cpython-38.pyc,, +virtualenv/run/__pycache__/session.cpython-38.pyc,, +virtualenv/run/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +virtualenv/run/plugin/__pycache__/__init__.cpython-38.pyc,, +virtualenv/run/plugin/__pycache__/activators.cpython-38.pyc,, +virtualenv/run/plugin/__pycache__/base.cpython-38.pyc,, +virtualenv/run/plugin/__pycache__/creators.cpython-38.pyc,, +virtualenv/run/plugin/__pycache__/discovery.cpython-38.pyc,, +virtualenv/run/plugin/__pycache__/seeders.cpython-38.pyc,, +virtualenv/run/plugin/activators.py,sha256=kmHShj36eHfbnsiAJzX0U5LYvGhe0WkRYjbuKDz6gVM,2117 +virtualenv/run/plugin/base.py,sha256=-2185C01PaxOG7gnMbWWyZlo24n_FYo5J5_naeNZw8s,1934 +virtualenv/run/plugin/creators.py,sha256=PIxJ85KmrQU7lUO-r8Znxbb4lTEzwHggc9lcDqmt2tc,3494 +virtualenv/run/plugin/discovery.py,sha256=3ykxRvPA1FJMkqsbr2TV0LBRPT5UCFeJdzEHfuEjxRM,1002 +virtualenv/run/plugin/seeders.py,sha256=c1mhzu0HNzKdif6YUV35fuAOS0XHFJz3TtccLW5fWG0,1074 +virtualenv/run/session.py,sha256=S4NZiHzij1vp895mN9s9ZwYobJjjdP37QOHCb1o-Ufo,2563 +virtualenv/seed/__init__.py,sha256=8ArZTco6Meo0W9i4dqnwmDO8BJYTaHX7oQx1o06vCm4,57 +virtualenv/seed/__pycache__/__init__.cpython-38.pyc,, +virtualenv/seed/__pycache__/seeder.cpython-38.pyc,, +virtualenv/seed/embed/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +virtualenv/seed/embed/__pycache__/__init__.cpython-38.pyc,, +virtualenv/seed/embed/__pycache__/base_embed.cpython-38.pyc,, +virtualenv/seed/embed/__pycache__/pip_invoke.cpython-38.pyc,, +virtualenv/seed/embed/base_embed.py,sha256=46mWtqWj_MjOQEqMJyosL0RWGL6HwrHAL2r1Jxc9DuI,4182 +virtualenv/seed/embed/pip_invoke.py,sha256=EMVwIeoW15SuorJ8z_-vBxPXwQJLS0ILA0Va9zNoOLI,2127 +virtualenv/seed/embed/via_app_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +virtualenv/seed/embed/via_app_data/__pycache__/__init__.cpython-38.pyc,, +virtualenv/seed/embed/via_app_data/__pycache__/via_app_data.cpython-38.pyc,, +virtualenv/seed/embed/via_app_data/pip_install/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +virtualenv/seed/embed/via_app_data/pip_install/__pycache__/__init__.cpython-38.pyc,, +virtualenv/seed/embed/via_app_data/pip_install/__pycache__/base.cpython-38.pyc,, +virtualenv/seed/embed/via_app_data/pip_install/__pycache__/copy.cpython-38.pyc,, +virtualenv/seed/embed/via_app_data/pip_install/__pycache__/symlink.cpython-38.pyc,, +virtualenv/seed/embed/via_app_data/pip_install/base.py,sha256=rnR60JzM7G04cPDo2eH-aR8-iQuFXBgHJ2lQnSf0Gfs,6355 +virtualenv/seed/embed/via_app_data/pip_install/copy.py,sha256=gG2NePFHOYh-bsCf6TpsaQ_qrYhdBy67k0RWuwHSAwo,1307 +virtualenv/seed/embed/via_app_data/pip_install/symlink.py,sha256=wHCpfKobvjjaZLUSwM3FSCblZfiBFw4IQYsxwlfEEu0,2362 +virtualenv/seed/embed/via_app_data/via_app_data.py,sha256=NkVhEFv4iuKG0qvEg4AAmucMwmQgNaPLB-Syepzgps0,5994 +virtualenv/seed/seeder.py,sha256=DSGE_8Ycj01vj8mkppUBA9h7JG76XsVBMt-5MWlMF6k,1178 +virtualenv/seed/wheels/__init__.py,sha256=1J7el7lNjAwGxM4dmricrbVhSbYxs5sPzv9PTx2A6qA,226 +virtualenv/seed/wheels/__pycache__/__init__.cpython-38.pyc,, +virtualenv/seed/wheels/__pycache__/acquire.cpython-38.pyc,, +virtualenv/seed/wheels/__pycache__/bundle.cpython-38.pyc,, +virtualenv/seed/wheels/__pycache__/periodic_update.cpython-38.pyc,, +virtualenv/seed/wheels/__pycache__/util.cpython-38.pyc,, +virtualenv/seed/wheels/acquire.py,sha256=qchqlIynLi2VP2VtdAfVfZJHbUPcLY2Ui5r7Eh-aZz8,4426 +virtualenv/seed/wheels/bundle.py,sha256=W0uVjClv9IBa50jRvPKm0jMwWnrYTEfDny2Z6bw2W7c,1835 +virtualenv/seed/wheels/embed/__init__.py,sha256=CLMKoeveDRyiNAdZjEtD38cepgNXkg65xzFu5OSHEus,1995 +virtualenv/seed/wheels/embed/__pycache__/__init__.cpython-38.pyc,, +virtualenv/seed/wheels/embed/pip-19.1.1-py2.py3-none-any.whl,sha256=mTE08EdUcbkUUsoCnUOQ3I8pisY6cSgU8QHNG220ZnY,1360957 +virtualenv/seed/wheels/embed/pip-20.3.1-py2.py3-none-any.whl,sha256=Ql55sgk5q7_6djOpEVGogq7cd1ZNkxPjWE6wQWwoxVg,1518513 +virtualenv/seed/wheels/embed/setuptools-43.0.0-py2.py3-none-any.whl,sha256=pn-qUVGe8ozYJhr_DiIbbkw3D4-4utqKo-etiUUZmWM,583228 +virtualenv/seed/wheels/embed/setuptools-44.1.1-py2.py3-none-any.whl,sha256=J6cUwJJTE05gpvpoEw94xwN-VWLE8h-PMY8q6QDRUtU,583493 +virtualenv/seed/wheels/embed/setuptools-50.3.2-py3-none-any.whl,sha256=LCQqCFb7rX775WDfSnrdkyTzQM9I30NlHpYEkkRmeUo,785194 +virtualenv/seed/wheels/embed/setuptools-51.0.0-py3-none-any.whl,sha256=jBd5NiFZRcmjfvgJraD6s2UZGVL3oSNhhDK7-sNTxSk,785164 +virtualenv/seed/wheels/embed/wheel-0.33.6-py2.py3-none-any.whl,sha256=9NoXY9O-zy4s2SoUp8kg8PAOyjD93p6pksg2aFufryg,21556 +virtualenv/seed/wheels/embed/wheel-0.36.1-py2.py3-none-any.whl,sha256=kGhk-3IsCrXy-cNbLGXjrzwAlALBCKcJwKyie8LJGHs,34788 +virtualenv/seed/wheels/periodic_update.py,sha256=HNVEuU2OYdWHW7lVO0h3NkpLkC8bu-5R7igJRXBnGDc,12792 +virtualenv/seed/wheels/util.py,sha256=Zdo76KEDqbNmM5u9JTuyu5uzEN_fQ4oj6qHOt0h0o1M,3960 +virtualenv/util/__init__.py,sha256=om6Hs2lH5igf5lkcSmQFiU7iMZ0Wx4dmSlMc6XW_Llg,199 +virtualenv/util/__pycache__/__init__.cpython-38.pyc,, +virtualenv/util/__pycache__/error.cpython-38.pyc,, +virtualenv/util/__pycache__/lock.cpython-38.pyc,, +virtualenv/util/__pycache__/six.cpython-38.pyc,, +virtualenv/util/__pycache__/zipapp.cpython-38.pyc,, +virtualenv/util/error.py,sha256=SRSZlXvMYQuJwxoUfNhlAyo3VwrAnIsZemSwPOxpjns,352 +virtualenv/util/lock.py,sha256=oFa0FcbE_TVDHOol44Mgtfa4D3ZjnVy-HSQx-y7ERKQ,4727 +virtualenv/util/path/__init__.py,sha256=YaBAxtzGBdMu0uUtppe0ZeCHw5HhO-5zjeb3-fzyMoI,336 +virtualenv/util/path/__pycache__/__init__.cpython-38.pyc,, +virtualenv/util/path/__pycache__/_permission.cpython-38.pyc,, +virtualenv/util/path/__pycache__/_sync.cpython-38.pyc,, +virtualenv/util/path/_pathlib/__init__.py,sha256=FjKCi8scB5MnHg2fLX5REoE0bOPkMXqpBEILVTeJZGQ,2130 +virtualenv/util/path/_pathlib/__pycache__/__init__.cpython-38.pyc,, +virtualenv/util/path/_pathlib/__pycache__/via_os_path.cpython-38.pyc,, +virtualenv/util/path/_pathlib/via_os_path.py,sha256=fYDFAX483zVvC9hAOAC9FYtrGdZethS0vtYtKsL5r-s,3772 +virtualenv/util/path/_permission.py,sha256=XpO2vGAk_92_biD4MEQcAQq2Zc8_rpm3M3n_hMUA1rw,745 +virtualenv/util/path/_sync.py,sha256=rheUrGsCqmhMwNs-uc5rDthNSUlsOrBJPoK8KZj3O1o,2393 +virtualenv/util/six.py,sha256=_8KWXUWi3-AaFmz4LkdyNra-uNuf70vlxwjN7oeRo8g,1463 +virtualenv/util/subprocess/__init__.py,sha256=1UmFrdBv2sVeUfZbDcO2yZpe28AE0ULOu9dRKlpJaa0,801 +virtualenv/util/subprocess/__pycache__/__init__.cpython-38.pyc,, +virtualenv/util/subprocess/__pycache__/_win_subprocess.cpython-38.pyc,, +virtualenv/util/subprocess/_win_subprocess.py,sha256=SChkXAKVbpehyrHod1ld76RSdTIalrgME1rtz5jUfm0,5655 +virtualenv/util/zipapp.py,sha256=jtf4Vn7XBnjPs_B_ObIQv_x4pFlIlPKAWHYLFV59h6U,1054 +virtualenv/version.py,sha256=_xEZqBMaab6kGqSyT5ckl2iWKIrWAaiI2XhEVuoJ4mI,65 diff --git a/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/WHEEL b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..01b8fc7d4a10cb8b4f1d21f11d3398d07d6b3478 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.36.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/entry_points.txt b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..3effb4ba117a2438858b9ba0db660ca33a376a53 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/entry_points.txt @@ -0,0 +1,32 @@ +[console_scripts] +virtualenv = virtualenv.__main__:run_with_catch + +[virtualenv.activate] +bash = virtualenv.activation.bash:BashActivator +batch = virtualenv.activation.batch:BatchActivator +cshell = virtualenv.activation.cshell:CShellActivator +fish = virtualenv.activation.fish:FishActivator +powershell = virtualenv.activation.powershell:PowerShellActivator +python = virtualenv.activation.python:PythonActivator +xonsh = virtualenv.activation.xonsh:XonshActivator + +[virtualenv.create] +cpython2-mac-framework = virtualenv.create.via_global_ref.builtin.cpython.mac_os:CPython2macOsFramework +cpython2-posix = virtualenv.create.via_global_ref.builtin.cpython.cpython2:CPython2Posix +cpython2-win = virtualenv.create.via_global_ref.builtin.cpython.cpython2:CPython2Windows +cpython3-mac-framework = virtualenv.create.via_global_ref.builtin.cpython.mac_os:CPython3macOsFramework +cpython3-posix = virtualenv.create.via_global_ref.builtin.cpython.cpython3:CPython3Posix +cpython3-win = virtualenv.create.via_global_ref.builtin.cpython.cpython3:CPython3Windows +pypy2-posix = virtualenv.create.via_global_ref.builtin.pypy.pypy2:PyPy2Posix +pypy2-win = virtualenv.create.via_global_ref.builtin.pypy.pypy2:Pypy2Windows +pypy3-posix = virtualenv.create.via_global_ref.builtin.pypy.pypy3:PyPy3Posix +pypy3-win = virtualenv.create.via_global_ref.builtin.pypy.pypy3:Pypy3Windows +venv = virtualenv.create.via_global_ref.venv:Venv + +[virtualenv.discovery] +builtin = virtualenv.discovery.builtin:Builtin + +[virtualenv.seed] +app-data = virtualenv.seed.embed.via_app_data.via_app_data:FromAppData +pip = virtualenv.seed.embed.pip_invoke:PipInvoke + diff --git a/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..66072c764505566d4de11368aeadd3c41318fa8f --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/top_level.txt @@ -0,0 +1 @@ +virtualenv diff --git a/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/zip-safe b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/zip-safe new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv-20.3.0.dist-info/zip-safe @@ -0,0 +1 @@ + diff --git a/robot/lib/python3.8/site-packages/virtualenv/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5f74e3ef215f1a04ca2bb385aa45caf909eed9f5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/__init__.py @@ -0,0 +1,10 @@ +from __future__ import absolute_import, unicode_literals + +from .run import cli_run, session_via_cli +from .version import __version__ + +__all__ = ( + "__version__", + "cli_run", + "session_via_cli", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/__main__.py b/robot/lib/python3.8/site-packages/virtualenv/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..0995e4c18bc87ab9c6724ccefb2a48be8374550b --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/__main__.py @@ -0,0 +1,77 @@ +from __future__ import absolute_import, print_function, unicode_literals + +import logging +import sys +from datetime import datetime + + +def run(args=None, options=None): + start = datetime.now() + from virtualenv.run import cli_run + from virtualenv.util.error import ProcessCallFailed + + if args is None: + args = sys.argv[1:] + try: + session = cli_run(args, options) + logging.warning(LogSession(session, start)) + except ProcessCallFailed as exception: + print("subprocess call failed for {} with code {}".format(exception.cmd, exception.code)) + print(exception.out, file=sys.stdout, end="") + print(exception.err, file=sys.stderr, end="") + raise SystemExit(exception.code) + + +class LogSession(object): + def __init__(self, session, start): + self.session = session + self.start = start + + def __str__(self): + from virtualenv.util.six import ensure_text + + spec = self.session.creator.interpreter.spec + elapsed = (datetime.now() - self.start).total_seconds() * 1000 + lines = [ + "created virtual environment {} in {:.0f}ms".format(spec, elapsed), + " creator {}".format(ensure_text(str(self.session.creator))), + ] + if self.session.seeder.enabled: + lines += ( + " seeder {}".format(ensure_text(str(self.session.seeder))), + " added seed packages: {}".format( + ", ".join( + sorted( + "==".join(i.stem.split("-")) + for i in self.session.creator.purelib.iterdir() + if i.suffix == ".dist-info" + ), + ), + ), + ) + if self.session.activators: + lines.append(" activators {}".format(",".join(i.__class__.__name__ for i in self.session.activators))) + return "\n".join(lines) + + +def run_with_catch(args=None): + from virtualenv.config.cli.parser import VirtualEnvOptions + + options = VirtualEnvOptions() + try: + run(args, options) + except (KeyboardInterrupt, SystemExit, Exception) as exception: + try: + if getattr(options, "with_traceback", False): + raise + else: + if not (isinstance(exception, SystemExit) and exception.code == 0): + logging.error("%s: %s", type(exception).__name__, exception) + code = exception.code if isinstance(exception, SystemExit) else 1 + sys.exit(code) + finally: + logging.shutdown() # force flush of log messages before the trace is printed + + +if __name__ == "__main__": # pragma: no cov + run_with_catch() # pragma: no cov diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/activation/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fa2f0b4af78b7eaa0643d752fa835e052e463aec --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/__init__.py @@ -0,0 +1,19 @@ +from __future__ import absolute_import, unicode_literals + +from .bash import BashActivator +from .batch import BatchActivator +from .cshell import CShellActivator +from .fish import FishActivator +from .powershell import PowerShellActivator +from .python import PythonActivator +from .xonsh import XonshActivator + +__all__ = [ + "BashActivator", + "PowerShellActivator", + "XonshActivator", + "CShellActivator", + "PythonActivator", + "BatchActivator", + "FishActivator", +] diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/activator.py b/robot/lib/python3.8/site-packages/virtualenv/activation/activator.py new file mode 100644 index 0000000000000000000000000000000000000000..587ac105bc52e59f41357861a258310122302bab --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/activator.py @@ -0,0 +1,44 @@ +from __future__ import absolute_import, unicode_literals + +from abc import ABCMeta, abstractmethod + +from six import add_metaclass + + +@add_metaclass(ABCMeta) +class Activator(object): + """Generates an activate script for the virtual environment""" + + def __init__(self, options): + """Create a new activator generator. + + :param options: the parsed options as defined within :meth:`add_parser_arguments` + """ + self.flag_prompt = options.prompt + + @classmethod + def supports(cls, interpreter): + """Check if the activation script is supported in the given interpreter. + + :param interpreter: the interpreter we need to support + :return: ``True`` if supported, ``False`` otherwise + """ + return True + + @classmethod + def add_parser_arguments(cls, parser, interpreter): + """ + Add CLI arguments for this activation script. + + :param parser: the CLI parser + :param interpreter: the interpreter this virtual environment is based of + """ + + @abstractmethod + def generate(self, creator): + """Generate the activate script for the given creator. + + :param creator: the creator (based of :class:`virtualenv.create.creator.Creator`) we used to create this \ + virtual environment + """ + raise NotImplementedError diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/bash/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/activation/bash/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..22c90c382718d2ef4d3ed55bcaf651af6f517200 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/bash/__init__.py @@ -0,0 +1,13 @@ +from __future__ import absolute_import, unicode_literals + +from virtualenv.util.path import Path + +from ..via_template import ViaTemplateActivator + + +class BashActivator(ViaTemplateActivator): + def templates(self): + yield Path("activate.sh") + + def as_name(self, template): + return template.stem diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/bash/activate.sh b/robot/lib/python3.8/site-packages/virtualenv/activation/bash/activate.sh new file mode 100644 index 0000000000000000000000000000000000000000..222d9820435177ad92e3659f436485a6e6aadc6d --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/bash/activate.sh @@ -0,0 +1,87 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + + +if [ "${BASH_SOURCE-}" = "$0" ]; then + echo "You must source this script: \$ source $0" >&2 + exit 33 +fi + +deactivate () { + unset -f pydoc >/dev/null 2>&1 + + # reset old environment variables + # ! [ -z ${VAR+_} ] returns true if VAR is declared at all + if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then + PATH="$_OLD_VIRTUAL_PATH" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then + PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ] ; then + hash -r 2>/dev/null + fi + + if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then + PS1="$_OLD_VIRTUAL_PS1" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + if [ ! "${1-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV='__VIRTUAL_ENV__' +if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then + VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV") +fi +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/__BIN_NAME__:$PATH" +export PATH + +# unset PYTHONHOME if set +if ! [ -z "${PYTHONHOME+_}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1-}" + if [ "x__VIRTUAL_PROMPT__" != x ] ; then + PS1="__VIRTUAL_PROMPT__${PS1-}" + else + PS1="(`basename \"$VIRTUAL_ENV\"`) ${PS1-}" + fi + export PS1 +fi + +# Make sure to unalias pydoc if it's already there +alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true + +pydoc () { + python -m pydoc "$@" +} + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ] ; then + hash -r 2>/dev/null +fi diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/batch/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/activation/batch/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4149712d879f3fa72792d4e8d8e885590af1d9b9 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/batch/__init__.py @@ -0,0 +1,23 @@ +from __future__ import absolute_import, unicode_literals + +import os + +from virtualenv.util.path import Path + +from ..via_template import ViaTemplateActivator + + +class BatchActivator(ViaTemplateActivator): + @classmethod + def supports(cls, interpreter): + return interpreter.os == "nt" + + def templates(self): + yield Path("activate.bat") + yield Path("deactivate.bat") + yield Path("pydoc.bat") + + def instantiate_template(self, replacements, template, creator): + # ensure the text has all newlines as \r\n - required by batch + base = super(BatchActivator, self).instantiate_template(replacements, template, creator) + return base.replace(os.linesep, "\n").replace("\n", os.linesep) diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/batch/activate.bat b/robot/lib/python3.8/site-packages/virtualenv/activation/batch/activate.bat new file mode 100644 index 0000000000000000000000000000000000000000..8dae28d19ae3ca5598474c04735b71a73596cfc7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/batch/activate.bat @@ -0,0 +1,40 @@ +@echo off + +set "VIRTUAL_ENV=__VIRTUAL_ENV__" + +if defined _OLD_VIRTUAL_PROMPT ( + set "PROMPT=%_OLD_VIRTUAL_PROMPT%" +) else ( + if not defined PROMPT ( + set "PROMPT=$P$G" + ) + if not defined VIRTUAL_ENV_DISABLE_PROMPT ( + set "_OLD_VIRTUAL_PROMPT=%PROMPT%" + ) +) +if not defined VIRTUAL_ENV_DISABLE_PROMPT ( + set "ENV_PROMPT=__VIRTUAL_PROMPT__" + if NOT DEFINED ENV_PROMPT ( + for %%d in ("%VIRTUAL_ENV%") do set "ENV_PROMPT=(%%~nxd) " + ) + ) + set "PROMPT=%ENV_PROMPT%%PROMPT%" +) + +REM Don't use () to avoid problems with them in %PATH% +if defined _OLD_VIRTUAL_PYTHONHOME goto ENDIFVHOME + set "_OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%" +:ENDIFVHOME + +set PYTHONHOME= + +REM if defined _OLD_VIRTUAL_PATH ( +if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH1 + set "PATH=%_OLD_VIRTUAL_PATH%" +:ENDIFVPATH1 +REM ) else ( +if defined _OLD_VIRTUAL_PATH goto ENDIFVPATH2 + set "_OLD_VIRTUAL_PATH=%PATH%" +:ENDIFVPATH2 + +set "PATH=%VIRTUAL_ENV%\__BIN_NAME__;%PATH%" diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/batch/deactivate.bat b/robot/lib/python3.8/site-packages/virtualenv/activation/batch/deactivate.bat new file mode 100644 index 0000000000000000000000000000000000000000..c33186657fa7ee2ee583c0e3d33b07b9527ece86 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/batch/deactivate.bat @@ -0,0 +1,19 @@ +@echo off + +set VIRTUAL_ENV= + +REM Don't use () to avoid problems with them in %PATH% +if not defined _OLD_VIRTUAL_PROMPT goto ENDIFVPROMPT + set "PROMPT=%_OLD_VIRTUAL_PROMPT%" + set _OLD_VIRTUAL_PROMPT= +:ENDIFVPROMPT + +if not defined _OLD_VIRTUAL_PYTHONHOME goto ENDIFVHOME + set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%" + set _OLD_VIRTUAL_PYTHONHOME= +:ENDIFVHOME + +if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH + set "PATH=%_OLD_VIRTUAL_PATH%" + set _OLD_VIRTUAL_PATH= +:ENDIFVPATH diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/batch/pydoc.bat b/robot/lib/python3.8/site-packages/virtualenv/activation/batch/pydoc.bat new file mode 100644 index 0000000000000000000000000000000000000000..45ddc132751ba8f752c0c3b4a6a70ad2f486d681 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/batch/pydoc.bat @@ -0,0 +1 @@ +python.exe -m pydoc %* diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/cshell/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/activation/cshell/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b25c602a5891927d0c14e45991a8bf53e082ade2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/cshell/__init__.py @@ -0,0 +1,14 @@ +from __future__ import absolute_import, unicode_literals + +from virtualenv.util.path import Path + +from ..via_template import ViaTemplateActivator + + +class CShellActivator(ViaTemplateActivator): + @classmethod + def supports(cls, interpreter): + return interpreter.os != "nt" + + def templates(self): + yield Path("activate.csh") diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/cshell/activate.csh b/robot/lib/python3.8/site-packages/virtualenv/activation/cshell/activate.csh new file mode 100644 index 0000000000000000000000000000000000000000..72b2cf8eff577696e9a6fc8789e515aa4777c0f5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/cshell/activate.csh @@ -0,0 +1,55 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. +# Created by Davide Di Blasi . + +set newline='\ +' + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH:q" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT:q" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate && unalias pydoc' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV '__VIRTUAL_ENV__' + +set _OLD_VIRTUAL_PATH="$PATH:q" +setenv PATH "$VIRTUAL_ENV:q/__BIN_NAME__:$PATH:q" + + + +if ('__VIRTUAL_PROMPT__' != "") then + set env_name = '__VIRTUAL_PROMPT__' +else + set env_name = '('"$VIRTUAL_ENV:t:q"') ' +endif + +if ( $?VIRTUAL_ENV_DISABLE_PROMPT ) then + if ( $VIRTUAL_ENV_DISABLE_PROMPT == "" ) then + set do_prompt = "1" + else + set do_prompt = "0" + endif +else + set do_prompt = "1" +endif + +if ( $do_prompt == "1" ) then + # Could be in a non-interactive environment, + # in which case, $prompt is undefined and we wouldn't + # care about the prompt anyway. + if ( $?prompt ) then + set _OLD_VIRTUAL_PROMPT="$prompt:q" + if ( "$prompt:q" =~ *"$newline:q"* ) then + : + else + set prompt = "$env_name:q$prompt:q" + endif + endif +endif + +unset env_name +unset do_prompt + +alias pydoc python -m pydoc + +rehash diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/fish/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/activation/fish/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8d0e19c2cdf5c017d75df874a43d0e3275fa454b --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/fish/__init__.py @@ -0,0 +1,10 @@ +from __future__ import absolute_import, unicode_literals + +from virtualenv.util.path import Path + +from ..via_template import ViaTemplateActivator + + +class FishActivator(ViaTemplateActivator): + def templates(self): + yield Path("activate.fish") diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/fish/activate.fish b/robot/lib/python3.8/site-packages/virtualenv/activation/fish/activate.fish new file mode 100644 index 0000000000000000000000000000000000000000..faa262270a8f4a355e41324e757d56d460640cd7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/fish/activate.fish @@ -0,0 +1,100 @@ +# This file must be used using `source bin/activate.fish` *within a running fish ( http://fishshell.com ) session*. +# Do not run it directly. + +function _bashify_path -d "Converts a fish path to something bash can recognize" + set fishy_path $argv + set bashy_path $fishy_path[1] + for path_part in $fishy_path[2..-1] + set bashy_path "$bashy_path:$path_part" + end + echo $bashy_path +end + +function _fishify_path -d "Converts a bash path to something fish can recognize" + echo $argv | tr ':' '\n' +end + +function deactivate -d 'Exit virtualenv mode and return to the normal environment.' + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + # https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling + if test (echo $FISH_VERSION | head -c 1) -lt 3 + set -gx PATH (_fishify_path "$_OLD_VIRTUAL_PATH") + else + set -gx PATH "$_OLD_VIRTUAL_PATH" + end + set -e _OLD_VIRTUAL_PATH + end + + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME "$_OLD_VIRTUAL_PYTHONHOME" + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + and functions -q _old_fish_prompt + # Set an empty local `$fish_function_path` to allow the removal of `fish_prompt` using `functions -e`. + set -l fish_function_path + + # Erase virtualenv's `fish_prompt` and restore the original. + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + set -e _OLD_FISH_PROMPT_OVERRIDE + end + + set -e VIRTUAL_ENV + + if test "$argv[1]" != 'nondestructive' + # Self-destruct! + functions -e pydoc + functions -e deactivate + functions -e _bashify_path + functions -e _fishify_path + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV '__VIRTUAL_ENV__' + +# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling +if test (echo $FISH_VERSION | head -c 1) -lt 3 + set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH) +else + set -gx _OLD_VIRTUAL_PATH "$PATH" +end +set -gx PATH "$VIRTUAL_ENV"'/__BIN_NAME__' $PATH + +# Unset `$PYTHONHOME` if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +function pydoc + python -m pydoc $argv +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # Copy the current `fish_prompt` function as `_old_fish_prompt`. + functions -c fish_prompt _old_fish_prompt + + function fish_prompt + # Run the user's prompt first; it might depend on (pipe)status. + set -l prompt (_old_fish_prompt) + + # Prompt override provided? + # If not, just prepend the environment name. + if test -n '__VIRTUAL_PROMPT__' + printf '%s%s' '__VIRTUAL_PROMPT__' (set_color normal) + else + printf '%s(%s) ' (set_color normal) (basename "$VIRTUAL_ENV") + end + + string join -- \n $prompt # handle multi-line prompts + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" +end diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/powershell/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/activation/powershell/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4fadc63bc18cc28832a529bc9e7fa733640d8139 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/powershell/__init__.py @@ -0,0 +1,10 @@ +from __future__ import absolute_import, unicode_literals + +from virtualenv.util.path import Path + +from ..via_template import ViaTemplateActivator + + +class PowerShellActivator(ViaTemplateActivator): + def templates(self): + yield Path("activate.ps1") diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/powershell/activate.ps1 b/robot/lib/python3.8/site-packages/virtualenv/activation/powershell/activate.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..a370a63f55450770f2f85921903135d85e396741 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/powershell/activate.ps1 @@ -0,0 +1,60 @@ +$script:THIS_PATH = $myinvocation.mycommand.path +$script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent + +function global:deactivate([switch] $NonDestructive) { + if (Test-Path variable:_OLD_VIRTUAL_PATH) { + $env:PATH = $variable:_OLD_VIRTUAL_PATH + Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global + } + + if (Test-Path function:_old_virtual_prompt) { + $function:prompt = $function:_old_virtual_prompt + Remove-Item function:\_old_virtual_prompt + } + + if ($env:VIRTUAL_ENV) { + Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue + } + + if (!$NonDestructive) { + # Self destruct! + Remove-Item function:deactivate + Remove-Item function:pydoc + } +} + +function global:pydoc { + python -m pydoc $args +} + +# unset irrelevant variables +deactivate -nondestructive + +$VIRTUAL_ENV = $BASE_DIR +$env:VIRTUAL_ENV = $VIRTUAL_ENV + +New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH + +$env:PATH = "$env:VIRTUAL_ENV/__BIN_NAME____PATH_SEP__" + $env:PATH +if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) { + function global:_old_virtual_prompt { + "" + } + $function:_old_virtual_prompt = $function:prompt + + if ("__VIRTUAL_PROMPT__" -ne "") { + function global:prompt { + # Add the custom prefix to the existing prompt + $previous_prompt_value = & $function:_old_virtual_prompt + ("__VIRTUAL_PROMPT__" + $previous_prompt_value) + } + } + else { + function global:prompt { + # Add a prefix to the current prompt, but don't discard it. + $previous_prompt_value = & $function:_old_virtual_prompt + $new_prompt_value = "($( Split-Path $env:VIRTUAL_ENV -Leaf )) " + ($new_prompt_value + $previous_prompt_value) + } + } +} diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/python/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/activation/python/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9e579124d74a98aee56a762fb02f93bc1c1f7b2d --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/python/__init__.py @@ -0,0 +1,35 @@ +from __future__ import absolute_import, unicode_literals + +import os +import sys +from collections import OrderedDict + +from virtualenv.util.path import Path +from virtualenv.util.six import ensure_text + +from ..via_template import ViaTemplateActivator + + +class PythonActivator(ViaTemplateActivator): + def templates(self): + yield Path("activate_this.py") + + def replacements(self, creator, dest_folder): + replacements = super(PythonActivator, self).replacements(creator, dest_folder) + lib_folders = OrderedDict((os.path.relpath(str(i), str(dest_folder)), None) for i in creator.libs) + win_py2 = creator.interpreter.platform == "win32" and creator.interpreter.version_info.major == 2 + replacements.update( + { + "__LIB_FOLDERS__": ensure_text(os.pathsep.join(lib_folders.keys())), + "__DECODE_PATH__": ("yes" if win_py2 else ""), + }, + ) + return replacements + + @staticmethod + def _repr_unicode(creator, value): + py2 = creator.interpreter.version_info.major == 2 + if py2: # on Python 2 we need to encode this into explicit utf-8, py3 supports unicode literals + start = 2 if sys.version_info[0] == 3 else 1 + value = ensure_text(repr(value.encode("utf-8"))[start:-1]) + return value diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/python/activate_this.py b/robot/lib/python3.8/site-packages/virtualenv/activation/python/activate_this.py new file mode 100644 index 0000000000000000000000000000000000000000..29debe3e748c7b209f2a63ab8550879b1929a1b3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/python/activate_this.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +"""Activate virtualenv for current interpreter: + +Use exec(open(this_file).read(), {'__file__': this_file}). + +This can be used when you must use an existing Python interpreter, not the virtualenv bin/python. +""" +import os +import site +import sys + +try: + abs_file = os.path.abspath(__file__) +except NameError: + raise AssertionError("You must use exec(open(this_file).read(), {'__file__': this_file}))") + +bin_dir = os.path.dirname(abs_file) +base = bin_dir[: -len("__BIN_NAME__") - 1] # strip away the bin part from the __file__, plus the path separator + +# prepend bin to PATH (this file is inside the bin directory) +os.environ["PATH"] = os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep)) +os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory + +# add the virtual environments libraries to the host python import mechanism +prev_length = len(sys.path) +for lib in "__LIB_FOLDERS__".split(os.pathsep): + path = os.path.realpath(os.path.join(bin_dir, lib)) + site.addsitedir(path.decode("utf-8") if "__DECODE_PATH__" else path) +sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length] + +sys.real_prefix = sys.prefix +sys.prefix = base diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/via_template.py b/robot/lib/python3.8/site-packages/virtualenv/activation/via_template.py new file mode 100644 index 0000000000000000000000000000000000000000..14f097973f580e474765c954f4d91608ed13f264 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/via_template.py @@ -0,0 +1,67 @@ +from __future__ import absolute_import, unicode_literals + +import os +import sys +from abc import ABCMeta, abstractmethod + +from six import add_metaclass + +from virtualenv.util.six import ensure_text + +from .activator import Activator + +if sys.version_info >= (3, 7): + from importlib.resources import read_binary +else: + from importlib_resources import read_binary + + +@add_metaclass(ABCMeta) +class ViaTemplateActivator(Activator): + @abstractmethod + def templates(self): + raise NotImplementedError + + def generate(self, creator): + dest_folder = creator.bin_dir + replacements = self.replacements(creator, dest_folder) + generated = self._generate(replacements, self.templates(), dest_folder, creator) + if self.flag_prompt is not None: + creator.pyenv_cfg["prompt"] = self.flag_prompt + return generated + + def replacements(self, creator, dest_folder): + return { + "__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt, + "__VIRTUAL_ENV__": ensure_text(str(creator.dest)), + "__VIRTUAL_NAME__": creator.env_name, + "__BIN_NAME__": ensure_text(str(creator.bin_dir.relative_to(creator.dest))), + "__PATH_SEP__": ensure_text(os.pathsep), + } + + def _generate(self, replacements, templates, to_folder, creator): + generated = [] + for template in templates: + text = self.instantiate_template(replacements, template, creator) + dest = to_folder / self.as_name(template) + # use write_bytes to avoid platform specific line normalization (\n -> \r\n) + dest.write_bytes(text.encode("utf-8")) + generated.append(dest) + return generated + + def as_name(self, template): + return template.name + + def instantiate_template(self, replacements, template, creator): + # read content as binary to avoid platform specific line normalization (\n -> \r\n) + binary = read_binary(self.__module__, str(template)) + text = binary.decode("utf-8", errors="strict") + for key, value in replacements.items(): + value = self._repr_unicode(creator, value) + text = text.replace(key, value) + return text + + @staticmethod + def _repr_unicode(creator, value): + # by default we just let it be unicode + return value diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/xonsh/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/activation/xonsh/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d92411c20f3e8bceeeddc56204c56c032c5885f5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/xonsh/__init__.py @@ -0,0 +1,14 @@ +from __future__ import absolute_import, unicode_literals + +from virtualenv.util.path import Path + +from ..via_template import ViaTemplateActivator + + +class XonshActivator(ViaTemplateActivator): + def templates(self): + yield Path("activate.xsh") + + @classmethod + def supports(cls, interpreter): + return interpreter.version_info >= (3, 5) diff --git a/robot/lib/python3.8/site-packages/virtualenv/activation/xonsh/activate.xsh b/robot/lib/python3.8/site-packages/virtualenv/activation/xonsh/activate.xsh new file mode 100644 index 0000000000000000000000000000000000000000..c77ea627863b063ebbea0255459a5e1091981812 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/activation/xonsh/activate.xsh @@ -0,0 +1,46 @@ +"""Xonsh activate script for virtualenv""" +from xonsh.tools import get_sep as _get_sep + +def _deactivate(args): + if "pydoc" in aliases: + del aliases["pydoc"] + + if ${...}.get("_OLD_VIRTUAL_PATH", ""): + $PATH = $_OLD_VIRTUAL_PATH + del $_OLD_VIRTUAL_PATH + + if ${...}.get("_OLD_VIRTUAL_PYTHONHOME", ""): + $PYTHONHOME = $_OLD_VIRTUAL_PYTHONHOME + del $_OLD_VIRTUAL_PYTHONHOME + + if "VIRTUAL_ENV" in ${...}: + del $VIRTUAL_ENV + + if "VIRTUAL_ENV_PROMPT" in ${...}: + del $VIRTUAL_ENV_PROMPT + + if "nondestructive" not in args: + # Self destruct! + del aliases["deactivate"] + + +# unset irrelevant variables +_deactivate(["nondestructive"]) +aliases["deactivate"] = _deactivate + +$VIRTUAL_ENV = r"__VIRTUAL_ENV__" + +$_OLD_VIRTUAL_PATH = $PATH +$PATH = $PATH[:] +$PATH.add($VIRTUAL_ENV + _get_sep() + "__BIN_NAME__", front=True, replace=True) + +if ${...}.get("PYTHONHOME", ""): + # unset PYTHONHOME if set + $_OLD_VIRTUAL_PYTHONHOME = $PYTHONHOME + del $PYTHONHOME + +$VIRTUAL_ENV_PROMPT = "__VIRTUAL_PROMPT__" +if not $VIRTUAL_ENV_PROMPT: + del $VIRTUAL_ENV_PROMPT + +aliases["pydoc"] = ["python", "-m", "pydoc"] diff --git a/robot/lib/python3.8/site-packages/virtualenv/app_data/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/app_data/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2df0cae5d3465204568ac0f4527b26d9e623cdf0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/app_data/__init__.py @@ -0,0 +1,57 @@ +""" +Application data stored by virtualenv. +""" +from __future__ import absolute_import, unicode_literals + +import logging +import os + +from appdirs import user_data_dir + +from .na import AppDataDisabled +from .read_only import ReadOnlyAppData +from .via_disk_folder import AppDataDiskFolder +from .via_tempdir import TempAppData + + +def _default_app_data_dir(): # type: () -> str + key = str("VIRTUALENV_OVERRIDE_APP_DATA") + if key in os.environ: + return os.environ[key] + else: + return user_data_dir(appname="virtualenv", appauthor="pypa") + + +def make_app_data(folder, **kwargs): + read_only = kwargs.pop("read_only") + if kwargs: # py3+ kwonly + raise TypeError("unexpected keywords: {}") + + if folder is None: + folder = _default_app_data_dir() + folder = os.path.abspath(folder) + + if read_only: + return ReadOnlyAppData(folder) + + if not os.path.isdir(folder): + try: + os.makedirs(folder) + logging.debug("created app data folder %s", folder) + except OSError as exception: + logging.info("could not create app data folder %s due to %r", folder, exception) + + if os.access(folder, os.W_OK): + return AppDataDiskFolder(folder) + else: + logging.debug("app data folder %s has no write access", folder) + return TempAppData() + + +__all__ = ( + "AppDataDisabled", + "AppDataDiskFolder", + "ReadOnlyAppData", + "TempAppData", + "make_app_data", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/app_data/base.py b/robot/lib/python3.8/site-packages/virtualenv/app_data/base.py new file mode 100644 index 0000000000000000000000000000000000000000..4ea54d9f64b1208abaae3fd09bc1b556f773f5e1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/app_data/base.py @@ -0,0 +1,95 @@ +""" +Application data stored by virtualenv. +""" +from __future__ import absolute_import, unicode_literals + +from abc import ABCMeta, abstractmethod +from contextlib import contextmanager + +import six + +from virtualenv.info import IS_ZIPAPP + + +@six.add_metaclass(ABCMeta) +class AppData(object): + """Abstract storage interface for the virtualenv application""" + + @abstractmethod + def close(self): + """called before virtualenv exits""" + + @abstractmethod + def reset(self): + """called when the user passes in the reset app data""" + + @abstractmethod + def py_info(self, path): + raise NotImplementedError + + @abstractmethod + def py_info_clear(self): + raise NotImplementedError + + @property + def can_update(self): + raise NotImplementedError + + @abstractmethod + def embed_update_log(self, distribution, for_py_version): + raise NotImplementedError + + @property + def house(self): + raise NotImplementedError + + @property + def transient(self): + raise NotImplementedError + + @abstractmethod + def wheel_image(self, for_py_version, name): + raise NotImplementedError + + @contextmanager + def ensure_extracted(self, path, to_folder=None): + """Some paths might be within the zipapp, unzip these to a path on the disk""" + if IS_ZIPAPP: + with self.extract(path, to_folder) as result: + yield result + else: + yield path + + @abstractmethod + @contextmanager + def extract(self, path, to_folder): + raise NotImplementedError + + @abstractmethod + @contextmanager + def locked(self, path): + raise NotImplementedError + + +@six.add_metaclass(ABCMeta) +class ContentStore(object): + @abstractmethod + def exists(self): + raise NotImplementedError + + @abstractmethod + def read(self): + raise NotImplementedError + + @abstractmethod + def write(self, content): + raise NotImplementedError + + @abstractmethod + def remove(self): + raise NotImplementedError + + @abstractmethod + @contextmanager + def locked(self): + pass diff --git a/robot/lib/python3.8/site-packages/virtualenv/app_data/na.py b/robot/lib/python3.8/site-packages/virtualenv/app_data/na.py new file mode 100644 index 0000000000000000000000000000000000000000..5f7200d3a3e6bf3854cd5f7124896cd5a4253c91 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/app_data/na.py @@ -0,0 +1,66 @@ +from __future__ import absolute_import, unicode_literals + +from contextlib import contextmanager + +from .base import AppData, ContentStore + + +class AppDataDisabled(AppData): + """No application cache available (most likely as we don't have write permissions)""" + + transient = True + can_update = False + + def __init__(self): + pass + + error = RuntimeError("no app data folder available, probably no write access to the folder") + + def close(self): + """do nothing""" + + def reset(self): + """do nothing""" + + def py_info(self, path): + return ContentStoreNA() + + def embed_update_log(self, distribution, for_py_version): + return ContentStoreNA() + + def extract(self, path, to_folder): + raise self.error + + @contextmanager + def locked(self, path): + """do nothing""" + yield + + @property + def house(self): + raise self.error + + def wheel_image(self, for_py_version, name): + raise self.error + + def py_info_clear(self): + """""" + + +class ContentStoreNA(ContentStore): + def exists(self): + return False + + def read(self): + """""" + return None + + def write(self, content): + """""" + + def remove(self): + """""" + + @contextmanager + def locked(self): + yield diff --git a/robot/lib/python3.8/site-packages/virtualenv/app_data/read_only.py b/robot/lib/python3.8/site-packages/virtualenv/app_data/read_only.py new file mode 100644 index 0000000000000000000000000000000000000000..858978cd088af9e7085525ae854a97e71d1ca9e6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/app_data/read_only.py @@ -0,0 +1,34 @@ +import os.path + +from virtualenv.util.lock import NoOpFileLock + +from .via_disk_folder import AppDataDiskFolder, PyInfoStoreDisk + + +class ReadOnlyAppData(AppDataDiskFolder): + can_update = False + + def __init__(self, folder): # type: (str) -> None + if not os.path.isdir(folder): + raise RuntimeError("read-only app data directory {} does not exist".format(folder)) + self.lock = NoOpFileLock(folder) + + def reset(self): # type: () -> None + raise RuntimeError("read-only app data does not support reset") + + def py_info_clear(self): # type: () -> None + raise NotImplementedError + + def py_info(self, path): + return _PyInfoStoreDiskReadOnly(self.py_info_at, path) + + def embed_update_log(self, distribution, for_py_version): + raise NotImplementedError + + +class _PyInfoStoreDiskReadOnly(PyInfoStoreDisk): + def write(self, content): + raise RuntimeError("read-only app data python info cannot be updated") + + +__all__ = ("ReadOnlyAppData",) diff --git a/robot/lib/python3.8/site-packages/virtualenv/app_data/via_disk_folder.py b/robot/lib/python3.8/site-packages/virtualenv/app_data/via_disk_folder.py new file mode 100644 index 0000000000000000000000000000000000000000..2243f1670e7775cbe86b7ba8b09257181cb11f9d --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/app_data/via_disk_folder.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +""" +A rough layout of the current storage goes as: + +virtualenv-app-data +├── py - +│   └── *.json/lock +├── wheel +│   ├── house +│ │ └── *.whl +│ └── -> 3.9 +│ ├── img- +│ │ └── image +│ │ └── -> CopyPipInstall / SymlinkPipInstall +│ │ └── -> pip-20.1.1-py2.py3-none-any +│ └── embed +│ └── 1 +│ └── *.json -> for every distribution contains data about newer embed versions and releases +└─── unzip + └── + ├── py_info.py + ├── debug.py + └── _virtualenv.py +""" +from __future__ import absolute_import, unicode_literals + +import json +import logging +from abc import ABCMeta +from contextlib import contextmanager +from hashlib import sha256 + +import six + +from virtualenv.util.lock import ReentrantFileLock +from virtualenv.util.path import safe_delete +from virtualenv.util.six import ensure_text +from virtualenv.util.zipapp import extract +from virtualenv.version import __version__ + +from .base import AppData, ContentStore + + +class AppDataDiskFolder(AppData): + """ + Store the application data on the disk within a folder layout. + """ + + transient = False + can_update = True + + def __init__(self, folder): + self.lock = ReentrantFileLock(folder) + + def __repr__(self): + return "{}({})".format(type(self).__name__, self.lock.path) + + def __str__(self): + return str(self.lock.path) + + def reset(self): + logging.debug("reset app data folder %s", self.lock.path) + safe_delete(self.lock.path) + + def close(self): + """do nothing""" + + @contextmanager + def locked(self, path): + path_lock = self.lock / path + with path_lock: + yield path_lock.path + + @contextmanager + def extract(self, path, to_folder): + if to_folder is not None: + root = ReentrantFileLock(to_folder()) + else: + root = self.lock / "unzip" / __version__ + with root.lock_for_key(path.name): + dest = root.path / path.name + if not dest.exists(): + extract(path, dest) + yield dest + + @property + def py_info_at(self): + return self.lock / "py_info" / "1" + + def py_info(self, path): + return PyInfoStoreDisk(self.py_info_at, path) + + def py_info_clear(self): + """""" + py_info_folder = self.py_info_at + with py_info_folder: + for filename in py_info_folder.path.iterdir(): + if filename.suffix == ".json": + with py_info_folder.lock_for_key(filename.stem): + if filename.exists(): + filename.unlink() + + def embed_update_log(self, distribution, for_py_version): + return EmbedDistributionUpdateStoreDisk(self.lock / "wheel" / for_py_version / "embed" / "1", distribution) + + @property + def house(self): + path = self.lock.path / "wheel" / "house" + path.mkdir(parents=True, exist_ok=True) + return path + + def wheel_image(self, for_py_version, name): + return self.lock.path / "wheel" / for_py_version / "image" / "1" / name + + +@six.add_metaclass(ABCMeta) +class JSONStoreDisk(ContentStore): + def __init__(self, in_folder, key, msg, msg_args): + self.in_folder = in_folder + self.key = key + self.msg = msg + self.msg_args = msg_args + (self.file,) + + @property + def file(self): + return self.in_folder.path / "{}.json".format(self.key) + + def exists(self): + return self.file.exists() + + def read(self): + data, bad_format = None, False + try: + data = json.loads(self.file.read_text()) + logging.debug("got {} from %s".format(self.msg), *self.msg_args) + return data + except ValueError: + bad_format = True + except Exception: # noqa + pass + if bad_format: + try: + self.remove() + except OSError: # reading and writing on the same file may cause race on multiple processes + pass + return None + + def remove(self): + self.file.unlink() + logging.debug("removed {} at %s".format(self.msg), *self.msg_args) + + @contextmanager + def locked(self): + with self.in_folder.lock_for_key(self.key): + yield + + def write(self, content): + folder = self.file.parent + folder.mkdir(parents=True, exist_ok=True) + self.file.write_text(ensure_text(json.dumps(content, sort_keys=True, indent=2))) + logging.debug("wrote {} at %s".format(self.msg), *self.msg_args) + + +class PyInfoStoreDisk(JSONStoreDisk): + def __init__(self, in_folder, path): + key = sha256(str(path).encode("utf-8") if six.PY3 else str(path)).hexdigest() + super(PyInfoStoreDisk, self).__init__(in_folder, key, "python info of %s", (path,)) + + +class EmbedDistributionUpdateStoreDisk(JSONStoreDisk): + def __init__(self, in_folder, distribution): + super(EmbedDistributionUpdateStoreDisk, self).__init__( + in_folder, + distribution, + "embed update of distribution %s", + (distribution,), + ) diff --git a/robot/lib/python3.8/site-packages/virtualenv/app_data/via_tempdir.py b/robot/lib/python3.8/site-packages/virtualenv/app_data/via_tempdir.py new file mode 100644 index 0000000000000000000000000000000000000000..112a3fe6bceb1e40ee33d438fdb1c43411a94340 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/app_data/via_tempdir.py @@ -0,0 +1,27 @@ +from __future__ import absolute_import, unicode_literals + +import logging +from tempfile import mkdtemp + +from virtualenv.util.path import safe_delete + +from .via_disk_folder import AppDataDiskFolder + + +class TempAppData(AppDataDiskFolder): + transient = True + can_update = False + + def __init__(self): + super(TempAppData, self).__init__(folder=mkdtemp()) + logging.debug("created temporary app data folder %s", self.lock.path) + + def reset(self): + """this is a temporary folder, is already empty to start with""" + + def close(self): + logging.debug("remove temporary app data folder %s", self.lock.path) + safe_delete(self.lock.path) + + def embed_update_log(self, distribution, for_py_version): + raise NotImplementedError diff --git a/robot/lib/python3.8/site-packages/virtualenv/config/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/config/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..01e6d4f49d5f5d8103b5120746b16d0cc56add77 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/config/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import, unicode_literals diff --git a/robot/lib/python3.8/site-packages/virtualenv/config/cli/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/config/cli/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..01e6d4f49d5f5d8103b5120746b16d0cc56add77 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/config/cli/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import, unicode_literals diff --git a/robot/lib/python3.8/site-packages/virtualenv/config/cli/parser.py b/robot/lib/python3.8/site-packages/virtualenv/config/cli/parser.py new file mode 100644 index 0000000000000000000000000000000000000000..eb4db30a70c9488d21e01fdd6289c400fab75fe0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/config/cli/parser.py @@ -0,0 +1,120 @@ +from __future__ import absolute_import, unicode_literals + +from argparse import SUPPRESS, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace +from collections import OrderedDict + +from virtualenv.config.convert import get_type + +from ..env_var import get_env_var +from ..ini import IniConfig + + +class VirtualEnvOptions(Namespace): + def __init__(self, **kwargs): + super(VirtualEnvOptions, self).__init__(**kwargs) + self._src = None + self._sources = {} + + def set_src(self, key, value, src): + setattr(self, key, value) + if src.startswith("env var"): + src = "env var" + self._sources[key] = src + + def __setattr__(self, key, value): + if getattr(self, "_src", None) is not None: + self._sources[key] = self._src + super(VirtualEnvOptions, self).__setattr__(key, value) + + def get_source(self, key): + return self._sources.get(key) + + @property + def verbosity(self): + if not hasattr(self, "verbose") and not hasattr(self, "quiet"): + return None + return max(self.verbose - self.quiet, 0) + + def __repr__(self): + return "{}({})".format( + type(self).__name__, + ", ".join("{}={}".format(k, v) for k, v in vars(self).items() if not k.startswith("_")), + ) + + +class VirtualEnvConfigParser(ArgumentParser): + """ + Custom option parser which updates its defaults by checking the configuration files and environmental variables + """ + + def __init__(self, options=None, *args, **kwargs): + self.file_config = IniConfig() + self.epilog_list = [] + kwargs["epilog"] = self.file_config.epilog + kwargs["add_help"] = False + kwargs["formatter_class"] = HelpFormatter + kwargs["prog"] = "virtualenv" + super(VirtualEnvConfigParser, self).__init__(*args, **kwargs) + self._fixed = set() + if options is not None and not isinstance(options, VirtualEnvOptions): + raise TypeError("options must be of type VirtualEnvOptions") + self.options = VirtualEnvOptions() if options is None else options + self._interpreter = None + self._app_data = None + + def _fix_defaults(self): + for action in self._actions: + action_id = id(action) + if action_id not in self._fixed: + self._fix_default(action) + self._fixed.add(action_id) + + def _fix_default(self, action): + if hasattr(action, "default") and hasattr(action, "dest") and action.default != SUPPRESS: + as_type = get_type(action) + names = OrderedDict((i.lstrip("-").replace("-", "_"), None) for i in action.option_strings) + outcome = None + for name in names: + outcome = get_env_var(name, as_type) + if outcome is not None: + break + if outcome is None and self.file_config: + for name in names: + outcome = self.file_config.get(name, as_type) + if outcome is not None: + break + if outcome is not None: + action.default, action.default_source = outcome + else: + outcome = action.default, "default" + self.options.set_src(action.dest, *outcome) + + def enable_help(self): + self._fix_defaults() + self.add_argument("-h", "--help", action="help", default=SUPPRESS, help="show this help message and exit") + + def parse_known_args(self, args=None, namespace=None): + if namespace is None: + namespace = self.options + elif namespace is not self.options: + raise ValueError("can only pass in parser.options") + self._fix_defaults() + self.options._src = "cli" + try: + return super(VirtualEnvConfigParser, self).parse_known_args(args, namespace=namespace) + finally: + self.options._src = None + + +class HelpFormatter(ArgumentDefaultsHelpFormatter): + def __init__(self, prog): + super(HelpFormatter, self).__init__(prog, max_help_position=32, width=240) + + def _get_help_string(self, action): + # noinspection PyProtectedMember + text = super(HelpFormatter, self)._get_help_string(action) + if hasattr(action, "default_source"): + default = " (default: %(default)s)" + if text.endswith(default): + text = "{} (default: %(default)s -> from %(default_source)s)".format(text[: -len(default)]) + return text diff --git a/robot/lib/python3.8/site-packages/virtualenv/config/convert.py b/robot/lib/python3.8/site-packages/virtualenv/config/convert.py new file mode 100644 index 0000000000000000000000000000000000000000..562720a57e390caefb4f75ba88c0a38921bb6987 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/config/convert.py @@ -0,0 +1,98 @@ +from __future__ import absolute_import, unicode_literals + +import logging +import os + + +class TypeData(object): + def __init__(self, default_type, as_type): + self.default_type = default_type + self.as_type = as_type + + def __repr__(self): + return "{}(base={}, as={})".format(self.__class__.__name__, self.default_type, self.as_type) + + def convert(self, value): + return self.default_type(value) + + +class BoolType(TypeData): + BOOLEAN_STATES = { + "1": True, + "yes": True, + "true": True, + "on": True, + "0": False, + "no": False, + "false": False, + "off": False, + } + + def convert(self, value): + if value.lower() not in self.BOOLEAN_STATES: + raise ValueError("Not a boolean: %s" % value) + return self.BOOLEAN_STATES[value.lower()] + + +class NoneType(TypeData): + def convert(self, value): + if not value: + return None + return str(value) + + +class ListType(TypeData): + def _validate(self): + """""" + + def convert(self, value, flatten=True): + values = self.split_values(value) + result = [] + for value in values: + sub_values = value.split(os.pathsep) + result.extend(sub_values) + converted = [self.as_type(i) for i in result] + return converted + + def split_values(self, value): + """Split the provided value into a list. + + First this is done by newlines. If there were no newlines in the text, + then we next try to split by comma. + """ + if isinstance(value, (str, bytes)): + # Use `splitlines` rather than a custom check for whether there is + # more than one line. This ensures that the full `splitlines()` + # logic is supported here. + values = value.splitlines() + if len(values) <= 1: + values = value.split(",") + values = filter(None, [x.strip() for x in values]) + else: + values = list(value) + + return values + + +def convert(value, as_type, source): + """Convert the value as a given type where the value comes from the given source""" + try: + return as_type.convert(value) + except Exception as exception: + logging.warning("%s failed to convert %r as %r because %r", source, value, as_type, exception) + raise + + +_CONVERT = {bool: BoolType, type(None): NoneType, list: ListType} + + +def get_type(action): + default_type = type(action.default) + as_type = default_type if action.type is None else action.type + return _CONVERT.get(default_type, TypeData)(default_type, as_type) + + +__all__ = ( + "convert", + "get_type", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/config/env_var.py b/robot/lib/python3.8/site-packages/virtualenv/config/env_var.py new file mode 100644 index 0000000000000000000000000000000000000000..259399a70526cdaa622383deb1320eef0740e436 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/config/env_var.py @@ -0,0 +1,29 @@ +from __future__ import absolute_import, unicode_literals + +import os + +from virtualenv.util.six import ensure_str, ensure_text + +from .convert import convert + + +def get_env_var(key, as_type): + """Get the environment variable option. + + :param key: the config key requested + :param as_type: the type we would like to convert it to + :return: + """ + environ_key = ensure_str("VIRTUALENV_{}".format(key.upper())) + if os.environ.get(environ_key): + value = os.environ[environ_key] + # noinspection PyBroadException + try: + source = "env var {}".format(ensure_text(environ_key)) + as_type = convert(value, as_type, source) + return as_type, source + except Exception: # note the converter already logs a warning when failures happen + pass + + +__all__ = ("get_env_var",) diff --git a/robot/lib/python3.8/site-packages/virtualenv/config/ini.py b/robot/lib/python3.8/site-packages/virtualenv/config/ini.py new file mode 100644 index 0000000000000000000000000000000000000000..4dec629a97a407d6e1d2c3531065867745fe3306 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/config/ini.py @@ -0,0 +1,83 @@ +from __future__ import absolute_import, unicode_literals + +import logging +import os + +from appdirs import user_config_dir + +from virtualenv.info import PY3 +from virtualenv.util import ConfigParser +from virtualenv.util.path import Path +from virtualenv.util.six import ensure_str + +from .convert import convert + + +class IniConfig(object): + VIRTUALENV_CONFIG_FILE_ENV_VAR = ensure_str("VIRTUALENV_CONFIG_FILE") + STATE = {None: "failed to parse", True: "active", False: "missing"} + + section = "virtualenv" + + def __init__(self): + config_file = os.environ.get(self.VIRTUALENV_CONFIG_FILE_ENV_VAR, None) + self.is_env_var = config_file is not None + config_file = ( + Path(config_file) + if config_file is not None + else Path(user_config_dir(appname="virtualenv", appauthor="pypa")) / "virtualenv.ini" + ) + self.config_file = config_file + self._cache = {} + + exception = None + self.has_config_file = None + try: + self.has_config_file = self.config_file.exists() + except OSError as exc: + exception = exc + else: + if self.has_config_file: + self.config_file = self.config_file.resolve() + self.config_parser = ConfigParser.ConfigParser() + try: + self._load() + self.has_virtualenv_section = self.config_parser.has_section(self.section) + except Exception as exc: + exception = exc + if exception is not None: + logging.error("failed to read config file %s because %r", config_file, exception) + + def _load(self): + with self.config_file.open("rt") as file_handler: + reader = getattr(self.config_parser, "read_file" if PY3 else "readfp") + reader(file_handler) + + def get(self, key, as_type): + cache_key = key, as_type + if cache_key in self._cache: + return self._cache[cache_key] + # noinspection PyBroadException + try: + source = "file" + raw_value = self.config_parser.get(self.section, key.lower()) + value = convert(raw_value, as_type, source) + result = value, source + except Exception: + result = None + self._cache[cache_key] = result + return result + + def __bool__(self): + return bool(self.has_config_file) and bool(self.has_virtualenv_section) + + @property + def epilog(self): + msg = "{}config file {} {} (change{} via env var {})" + return msg.format( + "\n", + self.config_file, + self.STATE[self.has_config_file], + "d" if self.is_env_var else "", + self.VIRTUALENV_CONFIG_FILE_ENV_VAR, + ) diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/create/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..01e6d4f49d5f5d8103b5120746b16d0cc56add77 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import, unicode_literals diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/creator.py b/robot/lib/python3.8/site-packages/virtualenv/create/creator.py new file mode 100644 index 0000000000000000000000000000000000000000..1b4ea69f66775b8d4936c5e7fbb109935a9e42da --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/creator.py @@ -0,0 +1,238 @@ +from __future__ import absolute_import, print_function, unicode_literals + +import json +import logging +import os +import sys +from abc import ABCMeta, abstractmethod +from argparse import ArgumentTypeError +from ast import literal_eval +from collections import OrderedDict +from textwrap import dedent + +from six import add_metaclass + +from virtualenv.discovery.cached_py_info import LogCmd +from virtualenv.info import WIN_CPYTHON_2 +from virtualenv.util.path import Path, safe_delete +from virtualenv.util.six import ensure_str, ensure_text +from virtualenv.util.subprocess import run_cmd +from virtualenv.version import __version__ + +from .pyenv_cfg import PyEnvCfg + +HERE = Path(os.path.abspath(__file__)).parent +DEBUG_SCRIPT = HERE / "debug.py" + + +class CreatorMeta(object): + def __init__(self): + self.error = None + + +@add_metaclass(ABCMeta) +class Creator(object): + """A class that given a python Interpreter creates a virtual environment""" + + def __init__(self, options, interpreter): + """Construct a new virtual environment creator. + + :param options: the CLI option as parsed from :meth:`add_parser_arguments` + :param interpreter: the interpreter to create virtual environment from + """ + self.interpreter = interpreter + self._debug = None + self.dest = Path(options.dest) + self.clear = options.clear + self.no_vcs_ignore = options.no_vcs_ignore + self.pyenv_cfg = PyEnvCfg.from_folder(self.dest) + self.app_data = options.app_data + + def __repr__(self): + return ensure_str(self.__unicode__()) + + def __unicode__(self): + return "{}({})".format(self.__class__.__name__, ", ".join("{}={}".format(k, v) for k, v in self._args())) + + def _args(self): + return [ + ("dest", ensure_text(str(self.dest))), + ("clear", self.clear), + ("no_vcs_ignore", self.no_vcs_ignore), + ] + + @classmethod + def can_create(cls, interpreter): + """Determine if we can create a virtual environment. + + :param interpreter: the interpreter in question + :return: ``None`` if we can't create, any other object otherwise that will be forwarded to \ + :meth:`add_parser_arguments` + """ + return True + + @classmethod + def add_parser_arguments(cls, parser, interpreter, meta, app_data): + """Add CLI arguments for the creator. + + :param parser: the CLI parser + :param app_data: the application data folder + :param interpreter: the interpreter we're asked to create virtual environment for + :param meta: value as returned by :meth:`can_create` + """ + parser.add_argument( + "dest", + help="directory to create virtualenv at", + type=cls.validate_dest, + ) + parser.add_argument( + "--clear", + dest="clear", + action="store_true", + help="remove the destination directory if exist before starting (will overwrite files otherwise)", + default=False, + ) + parser.add_argument( + "--no-vcs-ignore", + dest="no_vcs_ignore", + action="store_true", + help="don't create VCS ignore directive in the destination directory", + default=False, + ) + + @abstractmethod + def create(self): + """Perform the virtual environment creation.""" + raise NotImplementedError + + @classmethod + def validate_dest(cls, raw_value): + """No path separator in the path, valid chars and must be write-able""" + + def non_write_able(dest, value): + common = Path(*os.path.commonprefix([value.parts, dest.parts])) + raise ArgumentTypeError( + "the destination {} is not write-able at {}".format(dest.relative_to(common), common), + ) + + # the file system must be able to encode + # note in newer CPython this is always utf-8 https://www.python.org/dev/peps/pep-0529/ + encoding = sys.getfilesystemencoding() + refused = OrderedDict() + kwargs = {"errors": "ignore"} if encoding != "mbcs" else {} + for char in ensure_text(raw_value): + try: + trip = char.encode(encoding, **kwargs).decode(encoding) + if trip == char: + continue + raise ValueError(trip) + except ValueError: + refused[char] = None + if refused: + raise ArgumentTypeError( + "the file system codec ({}) cannot handle characters {!r} within {!r}".format( + encoding, + "".join(refused.keys()), + raw_value, + ), + ) + if os.pathsep in raw_value: + raise ArgumentTypeError( + "destination {!r} must not contain the path separator ({}) as this would break " + "the activation scripts".format(raw_value, os.pathsep), + ) + + value = Path(raw_value) + if value.exists() and value.is_file(): + raise ArgumentTypeError("the destination {} already exists and is a file".format(value)) + if (3, 3) <= sys.version_info <= (3, 6): + # pre 3.6 resolve is always strict, aka must exists, sidestep by using os.path operation + dest = Path(os.path.realpath(raw_value)) + else: + dest = Path(os.path.abspath(str(value))).resolve() # on Windows absolute does not imply resolve so use both + value = dest + while dest: + if dest.exists(): + if os.access(ensure_text(str(dest)), os.W_OK): + break + else: + non_write_able(dest, value) + base, _ = dest.parent, dest.name + if base == dest: + non_write_able(dest, value) # pragma: no cover + dest = base + return str(value) + + def run(self): + if self.dest.exists() and self.clear: + logging.debug("delete %s", self.dest) + safe_delete(self.dest) + self.create() + self.set_pyenv_cfg() + if not self.no_vcs_ignore: + self.setup_ignore_vcs() + + def set_pyenv_cfg(self): + self.pyenv_cfg.content = OrderedDict() + self.pyenv_cfg["home"] = self.interpreter.system_exec_prefix + self.pyenv_cfg["implementation"] = self.interpreter.implementation + self.pyenv_cfg["version_info"] = ".".join(str(i) for i in self.interpreter.version_info) + self.pyenv_cfg["virtualenv"] = __version__ + + def setup_ignore_vcs(self): + """Generate ignore instructions for version control systems.""" + # mark this folder to be ignored by VCS, handle https://www.python.org/dev/peps/pep-0610/#registered-vcs + git_ignore = self.dest / ".gitignore" + if not git_ignore.exists(): + git_ignore.write_text( + dedent( + """ + # created by virtualenv automatically + * + """, + ).lstrip(), + ) + # Mercurial - does not support the .hgignore file inside a subdirectory directly, but only if included via the + # subinclude directive from root, at which point on might as well ignore the directory itself, see + # https://www.selenic.com/mercurial/hgignore.5.html for more details + # Bazaar - does not support ignore files in sub-directories, only at root level via .bzrignore + # Subversion - does not support ignore files, requires direct manipulation with the svn tool + + @property + def debug(self): + """ + :return: debug information about the virtual environment (only valid after :meth:`create` has run) + """ + if self._debug is None and self.exe is not None: + self._debug = get_env_debug_info(self.exe, self.debug_script(), self.app_data) + return self._debug + + # noinspection PyMethodMayBeStatic + def debug_script(self): + return DEBUG_SCRIPT + + +def get_env_debug_info(env_exe, debug_script, app_data): + env = os.environ.copy() + env.pop(str("PYTHONPATH"), None) + + with app_data.ensure_extracted(debug_script) as debug_script: + cmd = [str(env_exe), str(debug_script)] + if WIN_CPYTHON_2: + cmd = [ensure_text(i) for i in cmd] + logging.debug(str("debug via %r"), LogCmd(cmd)) + code, out, err = run_cmd(cmd) + + # noinspection PyBroadException + try: + if code != 0: + result = literal_eval(out) + else: + result = json.loads(out) + if err: + result["err"] = err + except Exception as exception: + return {"out": out, "err": err, "returncode": code, "exception": repr(exception)} + if "sys" in result and "path" in result["sys"]: + del result["sys"]["path"][0] + return result diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/debug.py b/robot/lib/python3.8/site-packages/virtualenv/create/debug.py new file mode 100644 index 0000000000000000000000000000000000000000..0cdaa494124dd60b06432d384500450a5120a05b --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/debug.py @@ -0,0 +1,110 @@ +"""Inspect a target Python interpreter virtual environment wise""" +import sys # built-in + +PYPY2_WIN = hasattr(sys, "pypy_version_info") and sys.platform != "win32" and sys.version_info[0] == 2 + + +def encode_path(value): + if value is None: + return None + if not isinstance(value, (str, bytes)): + if isinstance(value, type): + value = repr(value) + else: + value = repr(type(value)) + if isinstance(value, bytes) and not PYPY2_WIN: + value = value.decode(sys.getfilesystemencoding()) + return value + + +def encode_list_path(value): + return [encode_path(i) for i in value] + + +def run(): + """print debug data about the virtual environment""" + try: + from collections import OrderedDict + except ImportError: # pragma: no cover + # this is possible if the standard library cannot be accessed + # noinspection PyPep8Naming + OrderedDict = dict # pragma: no cover + result = OrderedDict([("sys", OrderedDict())]) + path_keys = ( + "executable", + "_base_executable", + "prefix", + "base_prefix", + "real_prefix", + "exec_prefix", + "base_exec_prefix", + "path", + "meta_path", + ) + for key in path_keys: + value = getattr(sys, key, None) + if isinstance(value, list): + value = encode_list_path(value) + else: + value = encode_path(value) + result["sys"][key] = value + result["sys"]["fs_encoding"] = sys.getfilesystemencoding() + result["sys"]["io_encoding"] = getattr(sys.stdout, "encoding", None) + result["version"] = sys.version + + try: + import sysconfig + + # https://bugs.python.org/issue22199 + makefile = getattr(sysconfig, "get_makefile_filename", getattr(sysconfig, "_get_makefile_filename", None)) + result["makefile_filename"] = encode_path(makefile()) + except ImportError: + pass + + import os # landmark + + result["os"] = repr(os) + + try: + # noinspection PyUnresolvedReferences + import site # site + + result["site"] = repr(site) + except ImportError as exception: # pragma: no cover + result["site"] = repr(exception) # pragma: no cover + + try: + # noinspection PyUnresolvedReferences + import datetime # site + + result["datetime"] = repr(datetime) + except ImportError as exception: # pragma: no cover + result["datetime"] = repr(exception) # pragma: no cover + + try: + # noinspection PyUnresolvedReferences + import math # site + + result["math"] = repr(math) + except ImportError as exception: # pragma: no cover + result["math"] = repr(exception) # pragma: no cover + + # try to print out, this will validate if other core modules are available (json in this case) + try: + import json + + result["json"] = repr(json) + except ImportError as exception: + result["json"] = repr(exception) + else: + try: + content = json.dumps(result, indent=2) + sys.stdout.write(content) + except (ValueError, TypeError) as exception: # pragma: no cover + sys.stderr.write(repr(exception)) + sys.stdout.write(repr(result)) # pragma: no cover + raise SystemExit(1) # pragma: no cover + + +if __name__ == "__main__": + run() diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/describe.py b/robot/lib/python3.8/site-packages/virtualenv/create/describe.py new file mode 100644 index 0000000000000000000000000000000000000000..1e59aaeae04f212c1fb83afcd0e69c2689187eb3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/describe.py @@ -0,0 +1,117 @@ +from __future__ import absolute_import, print_function, unicode_literals + +from abc import ABCMeta +from collections import OrderedDict + +from six import add_metaclass + +from virtualenv.info import IS_WIN +from virtualenv.util.path import Path +from virtualenv.util.six import ensure_text + + +@add_metaclass(ABCMeta) +class Describe(object): + """Given a host interpreter tell us information about what the created interpreter might look like""" + + suffix = ".exe" if IS_WIN else "" + + def __init__(self, dest, interpreter): + self.interpreter = interpreter + self.dest = dest + self._stdlib = None + self._stdlib_platform = None + self._system_stdlib = None + self._conf_vars = None + + @property + def bin_dir(self): + return self.script_dir + + @property + def script_dir(self): + return self.dest / Path(self.interpreter.distutils_install["scripts"]) + + @property + def purelib(self): + return self.dest / self.interpreter.distutils_install["purelib"] + + @property + def platlib(self): + return self.dest / self.interpreter.distutils_install["platlib"] + + @property + def libs(self): + return list(OrderedDict(((self.platlib, None), (self.purelib, None))).keys()) + + @property + def stdlib(self): + if self._stdlib is None: + self._stdlib = Path(self.interpreter.sysconfig_path("stdlib", config_var=self._config_vars)) + return self._stdlib + + @property + def stdlib_platform(self): + if self._stdlib_platform is None: + self._stdlib_platform = Path(self.interpreter.sysconfig_path("platstdlib", config_var=self._config_vars)) + return self._stdlib_platform + + @property + def _config_vars(self): + if self._conf_vars is None: + self._conf_vars = self._calc_config_vars(ensure_text(str(self.dest))) + return self._conf_vars + + def _calc_config_vars(self, to): + return { + k: (to if v.startswith(self.interpreter.prefix) else v) for k, v in self.interpreter.sysconfig_vars.items() + } + + @classmethod + def can_describe(cls, interpreter): + """Knows means it knows how the output will look""" + return True + + @property + def env_name(self): + return ensure_text(self.dest.parts[-1]) + + @property + def exe(self): + return self.bin_dir / "{}{}".format(self.exe_stem(), self.suffix) + + @classmethod + def exe_stem(cls): + """executable name without suffix - there seems to be no standard way to get this without creating it""" + raise NotImplementedError + + def script(self, name): + return self.script_dir / "{}{}".format(name, self.suffix) + + +@add_metaclass(ABCMeta) +class Python2Supports(Describe): + @classmethod + def can_describe(cls, interpreter): + return interpreter.version_info.major == 2 and super(Python2Supports, cls).can_describe(interpreter) + + +@add_metaclass(ABCMeta) +class Python3Supports(Describe): + @classmethod + def can_describe(cls, interpreter): + return interpreter.version_info.major == 3 and super(Python3Supports, cls).can_describe(interpreter) + + +@add_metaclass(ABCMeta) +class PosixSupports(Describe): + @classmethod + def can_describe(cls, interpreter): + return interpreter.os == "posix" and super(PosixSupports, cls).can_describe(interpreter) + + +@add_metaclass(ABCMeta) +class WindowsSupports(Describe): + @classmethod + def can_describe(cls, interpreter): + return interpreter.os == "nt" and super(WindowsSupports, cls).can_describe(interpreter) diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/pyenv_cfg.py b/robot/lib/python3.8/site-packages/virtualenv/create/pyenv_cfg.py new file mode 100644 index 0000000000000000000000000000000000000000..1a8d824401d26c725f731814306daea65f260bbe --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/pyenv_cfg.py @@ -0,0 +1,61 @@ +from __future__ import absolute_import, unicode_literals + +import logging +from collections import OrderedDict + +from virtualenv.util.six import ensure_text + + +class PyEnvCfg(object): + def __init__(self, content, path): + self.content = content + self.path = path + + @classmethod + def from_folder(cls, folder): + return cls.from_file(folder / "pyvenv.cfg") + + @classmethod + def from_file(cls, path): + content = cls._read_values(path) if path.exists() else OrderedDict() + return PyEnvCfg(content, path) + + @staticmethod + def _read_values(path): + content = OrderedDict() + for line in path.read_text(encoding="utf-8").splitlines(): + equals_at = line.index("=") + key = line[:equals_at].strip() + value = line[equals_at + 1 :].strip() + content[key] = value + return content + + def write(self): + logging.debug("write %s", ensure_text(str(self.path))) + text = "" + for key, value in self.content.items(): + line = "{} = {}".format(key, value) + logging.debug("\t%s", line) + text += line + text += "\n" + self.path.write_text(text, encoding="utf-8") + + def refresh(self): + self.content = self._read_values(self.path) + return self.content + + def __setitem__(self, key, value): + self.content[key] = value + + def __getitem__(self, key): + return self.content[key] + + def __contains__(self, item): + return item in self.content + + def update(self, other): + self.content.update(other) + return self + + def __repr__(self): + return "{}(path={})".format(self.__class__.__name__, self.path) diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/_virtualenv.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/_virtualenv.py new file mode 100644 index 0000000000000000000000000000000000000000..da98b827a2461bd42b9517239dacad80fb0b46e2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/_virtualenv.py @@ -0,0 +1,130 @@ +"""Patches that are applied at runtime to the virtual environment""" +# -*- coding: utf-8 -*- + +import os +import sys + +VIRTUALENV_PATCH_FILE = os.path.join(__file__) + + +def patch_dist(dist): + """ + Distutils allows user to configure some arguments via a configuration file: + https://docs.python.org/3/install/index.html#distutils-configuration-files + + Some of this arguments though don't make sense in context of the virtual environment files, let's fix them up. + """ + # we cannot allow some install config as that would get packages installed outside of the virtual environment + old_parse_config_files = dist.Distribution.parse_config_files + + def parse_config_files(self, *args, **kwargs): + result = old_parse_config_files(self, *args, **kwargs) + install = self.get_option_dict("install") + + if "prefix" in install: # the prefix governs where to install the libraries + install["prefix"] = VIRTUALENV_PATCH_FILE, os.path.abspath(sys.prefix) + for base in ("purelib", "platlib", "headers", "scripts", "data"): + key = "install_{}".format(base) + if key in install: # do not allow global configs to hijack venv paths + install.pop(key, None) + return result + + dist.Distribution.parse_config_files = parse_config_files + + +# Import hook that patches some modules to ignore configuration values that break package installation in case +# of virtual environments. +_DISTUTILS_PATCH = "distutils.dist", "setuptools.dist" +if sys.version_info > (3, 4): + # https://docs.python.org/3/library/importlib.html#setting-up-an-importer + from functools import partial + from importlib.abc import MetaPathFinder + from importlib.util import find_spec + + class _Finder(MetaPathFinder): + """A meta path finder that allows patching the imported distutils modules""" + + fullname = None + + # lock[0] is threading.Lock(), but initialized lazily to avoid importing threading very early at startup, + # because there are gevent-based applications that need to be first to import threading by themselves. + # See https://github.com/pypa/virtualenv/issues/1895 for details. + lock = [] + + def find_spec(self, fullname, path, target=None): + if fullname in _DISTUTILS_PATCH and self.fullname is None: + # initialize lock[0] lazily + if len(self.lock) == 0: + import threading + + lock = threading.Lock() + # there is possibility that two threads T1 and T2 are simultaneously running into find_spec, + # observing .lock as empty, and further going into hereby initialization. However due to the GIL, + # list.append() operation is atomic and this way only one of the threads will "win" to put the lock + # - that every thread will use - into .lock[0]. + # https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe + self.lock.append(lock) + + with self.lock[0]: + self.fullname = fullname + try: + spec = find_spec(fullname, path) + if spec is not None: + # https://www.python.org/dev/peps/pep-0451/#how-loading-will-work + is_new_api = hasattr(spec.loader, "exec_module") + func_name = "exec_module" if is_new_api else "load_module" + old = getattr(spec.loader, func_name) + func = self.exec_module if is_new_api else self.load_module + if old is not func: + try: + setattr(spec.loader, func_name, partial(func, old)) + except AttributeError: + pass # C-Extension loaders are r/o such as zipimporter with ver >= (3, 7) or (3, 8, 3) > ver >= (3, 8)) + + +class CPython3Windows(CPythonWindows, CPython3): + """""" + + @classmethod + def setup_meta(cls, interpreter): + if is_store_python(interpreter): # store python is not supported here + return None + return super(CPython3Windows, cls).setup_meta(interpreter) + + @classmethod + def sources(cls, interpreter): + for src in super(CPython3Windows, cls).sources(interpreter): + yield src + if not cls.has_shim(interpreter): + for src in cls.include_dll_and_pyd(interpreter): + yield src + + @classmethod + def has_shim(cls, interpreter): + return interpreter.version_info.minor >= 7 and cls.shim(interpreter) is not None + + @classmethod + def shim(cls, interpreter): + shim = Path(interpreter.system_stdlib) / "venv" / "scripts" / "nt" / "python.exe" + if shim.exists(): + return shim + return None + + @classmethod + def host_python(cls, interpreter): + if cls.has_shim(interpreter): + # starting with CPython 3.7 Windows ships with a venvlauncher.exe that avoids the need for dll/pyd copies + # it also means the wrapper must be copied to avoid bugs such as https://bugs.python.org/issue42013 + return cls.shim(interpreter) + return super(CPython3Windows, cls).host_python(interpreter) + + @classmethod + def include_dll_and_pyd(cls, interpreter): + dll_folder = Path(interpreter.system_prefix) / "DLLs" + host_exe_folder = Path(interpreter.system_executable).parent + for folder in [host_exe_folder, dll_folder]: + for file in folder.iterdir(): + if file.suffix in (".pyd", ".dll"): + yield PathRefToDest(file, dest=cls.to_dll_and_pyd) + + def to_dll_and_pyd(self, src): + return self.bin_dir / src.name diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py new file mode 100644 index 0000000000000000000000000000000000000000..53f65e3418084abe302ccddc59ce65a117c5f31a --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +"""The Apple Framework builds require their own customization""" +import logging +import os +import struct +import subprocess +from abc import ABCMeta, abstractmethod +from textwrap import dedent + +from six import add_metaclass + +from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, PathRefToDest, RefMust +from virtualenv.util.path import Path +from virtualenv.util.six import ensure_text + +from .common import CPython, CPythonPosix, is_mac_os_framework +from .cpython2 import CPython2PosixBase +from .cpython3 import CPython3 + + +@add_metaclass(ABCMeta) +class CPythonmacOsFramework(CPython): + @classmethod + def can_describe(cls, interpreter): + return is_mac_os_framework(interpreter) and super(CPythonmacOsFramework, cls).can_describe(interpreter) + + @classmethod + def sources(cls, interpreter): + for src in super(CPythonmacOsFramework, cls).sources(interpreter): + yield src + # add a symlink to the host python image + exe = cls.image_ref(interpreter) + ref = PathRefToDest(exe, dest=lambda self, _: self.dest / ".Python", must=RefMust.SYMLINK) + yield ref + + def create(self): + super(CPythonmacOsFramework, self).create() + + # change the install_name of the copied python executables + target = "@executable_path/../.Python" + current = self.current_mach_o_image_path() + for src in self._sources: + if isinstance(src, ExePathRefToDest): + if src.must == RefMust.COPY or not self.symlinks: + exes = [self.bin_dir / src.base] + if not self.symlinks: + exes.extend(self.bin_dir / a for a in src.aliases) + for exe in exes: + fix_mach_o(str(exe), current, target, self.interpreter.max_size) + + @classmethod + def _executables(cls, interpreter): + for _, targets, must, when in super(CPythonmacOsFramework, cls)._executables(interpreter): + # Make sure we use the embedded interpreter inside the framework, even if sys.executable points to the + # stub executable in ${sys.prefix}/bin. + # See http://groups.google.com/group/python-virtualenv/browse_thread/thread/17cab2f85da75951 + fixed_host_exe = Path(interpreter.prefix) / "Resources" / "Python.app" / "Contents" / "MacOS" / "Python" + yield fixed_host_exe, targets, must, when + + @abstractmethod + def current_mach_o_image_path(self): + raise NotImplementedError + + @classmethod + def image_ref(cls, interpreter): + raise NotImplementedError + + +class CPython2macOsFramework(CPythonmacOsFramework, CPython2PosixBase): + @classmethod + def image_ref(cls, interpreter): + return Path(interpreter.prefix) / "Python" + + def current_mach_o_image_path(self): + return os.path.join(self.interpreter.prefix, "Python") + + @classmethod + def sources(cls, interpreter): + for src in super(CPython2macOsFramework, cls).sources(interpreter): + yield src + # landmark for exec_prefix + exec_marker_file, to_path, _ = cls.from_stdlib(cls.mappings(interpreter), "lib-dynload") + yield PathRefToDest(exec_marker_file, dest=to_path) + + @property + def reload_code(self): + result = super(CPython2macOsFramework, self).reload_code + result = dedent( + """ + # the bundled site.py always adds the global site package if we're on python framework build, escape this + import sysconfig + config = sysconfig.get_config_vars() + before = config["PYTHONFRAMEWORK"] + try: + config["PYTHONFRAMEWORK"] = "" + {} + finally: + config["PYTHONFRAMEWORK"] = before + """.format( + result, + ), + ) + return result + + +class CPython3macOsFramework(CPythonmacOsFramework, CPython3, CPythonPosix): + @classmethod + def image_ref(cls, interpreter): + return Path(interpreter.prefix) / "Python3" + + def current_mach_o_image_path(self): + return "@executable_path/../../../../Python3" + + @property + def reload_code(self): + result = super(CPython3macOsFramework, self).reload_code + result = dedent( + """ + # the bundled site.py always adds the global site package if we're on python framework build, escape this + import sys + before = sys._framework + try: + sys._framework = None + {} + finally: + sys._framework = before + """.format( + result, + ), + ) + return result + + +def fix_mach_o(exe, current, new, max_size): + """ + https://en.wikipedia.org/wiki/Mach-O + + Mach-O, short for Mach object file format, is a file format for executables, object code, shared libraries, + dynamically-loaded code, and core dumps. A replacement for the a.out format, Mach-O offers more extensibility and + faster access to information in the symbol table. + + Each Mach-O file is made up of one Mach-O header, followed by a series of load commands, followed by one or more + segments, each of which contains between 0 and 255 sections. Mach-O uses the REL relocation format to handle + references to symbols. When looking up symbols Mach-O uses a two-level namespace that encodes each symbol into an + 'object/symbol name' pair that is then linearly searched for by first the object and then the symbol name. + + The basic structure—a list of variable-length "load commands" that reference pages of data elsewhere in the file—was + also used in the executable file format for Accent. The Accent file format was in turn, based on an idea from Spice + Lisp. + + With the introduction of Mac OS X 10.6 platform the Mach-O file underwent a significant modification that causes + binaries compiled on a computer running 10.6 or later to be (by default) executable only on computers running Mac + OS X 10.6 or later. The difference stems from load commands that the dynamic linker, in previous Mac OS X versions, + does not understand. Another significant change to the Mach-O format is the change in how the Link Edit tables + (found in the __LINKEDIT section) function. In 10.6 these new Link Edit tables are compressed by removing unused and + unneeded bits of information, however Mac OS X 10.5 and earlier cannot read this new Link Edit table format. + """ + try: + logging.debug(u"change Mach-O for %s from %s to %s", ensure_text(exe), current, ensure_text(new)) + _builtin_change_mach_o(max_size)(exe, current, new) + except Exception as e: + logging.warning("Could not call _builtin_change_mac_o: %s. " "Trying to call install_name_tool instead.", e) + try: + cmd = ["install_name_tool", "-change", current, new, exe] + subprocess.check_call(cmd) + except Exception: + logging.fatal("Could not call install_name_tool -- you must " "have Apple's development tools installed") + raise + + +def _builtin_change_mach_o(maxint): + MH_MAGIC = 0xFEEDFACE + MH_CIGAM = 0xCEFAEDFE + MH_MAGIC_64 = 0xFEEDFACF + MH_CIGAM_64 = 0xCFFAEDFE + FAT_MAGIC = 0xCAFEBABE + BIG_ENDIAN = ">" + LITTLE_ENDIAN = "<" + LC_LOAD_DYLIB = 0xC + + class FileView(object): + """A proxy for file-like objects that exposes a given view of a file. Modified from macholib.""" + + def __init__(self, file_obj, start=0, size=maxint): + if isinstance(file_obj, FileView): + self._file_obj = file_obj._file_obj + else: + self._file_obj = file_obj + self._start = start + self._end = start + size + self._pos = 0 + + def __repr__(self): + return "".format(self._start, self._end, self._file_obj) + + def tell(self): + return self._pos + + def _checkwindow(self, seek_to, op): + if not (self._start <= seek_to <= self._end): + msg = "{} to offset {:d} is outside window [{:d}, {:d}]".format(op, seek_to, self._start, self._end) + raise IOError(msg) + + def seek(self, offset, whence=0): + seek_to = offset + if whence == os.SEEK_SET: + seek_to += self._start + elif whence == os.SEEK_CUR: + seek_to += self._start + self._pos + elif whence == os.SEEK_END: + seek_to += self._end + else: + raise IOError("Invalid whence argument to seek: {!r}".format(whence)) + self._checkwindow(seek_to, "seek") + self._file_obj.seek(seek_to) + self._pos = seek_to - self._start + + def write(self, content): + here = self._start + self._pos + self._checkwindow(here, "write") + self._checkwindow(here + len(content), "write") + self._file_obj.seek(here, os.SEEK_SET) + self._file_obj.write(content) + self._pos += len(content) + + def read(self, size=maxint): + assert size >= 0 + here = self._start + self._pos + self._checkwindow(here, "read") + size = min(size, self._end - here) + self._file_obj.seek(here, os.SEEK_SET) + read_bytes = self._file_obj.read(size) + self._pos += len(read_bytes) + return read_bytes + + def read_data(file, endian, num=1): + """Read a given number of 32-bits unsigned integers from the given file with the given endianness.""" + res = struct.unpack(endian + "L" * num, file.read(num * 4)) + if len(res) == 1: + return res[0] + return res + + def mach_o_change(at_path, what, value): + """Replace a given name (what) in any LC_LOAD_DYLIB command found in the given binary with a new name (value), + provided it's shorter.""" + + def do_macho(file, bits, endian): + # Read Mach-O header (the magic number is assumed read by the caller) + cpu_type, cpu_sub_type, file_type, n_commands, size_of_commands, flags = read_data(file, endian, 6) + # 64-bits header has one more field. + if bits == 64: + read_data(file, endian) + # The header is followed by n commands + for _ in range(n_commands): + where = file.tell() + # Read command header + cmd, cmd_size = read_data(file, endian, 2) + if cmd == LC_LOAD_DYLIB: + # The first data field in LC_LOAD_DYLIB commands is the offset of the name, starting from the + # beginning of the command. + name_offset = read_data(file, endian) + file.seek(where + name_offset, os.SEEK_SET) + # Read the NUL terminated string + load = file.read(cmd_size - name_offset).decode() + load = load[: load.index("\0")] + # If the string is what is being replaced, overwrite it. + if load == what: + file.seek(where + name_offset, os.SEEK_SET) + file.write(value.encode() + b"\0") + # Seek to the next command + file.seek(where + cmd_size, os.SEEK_SET) + + def do_file(file, offset=0, size=maxint): + file = FileView(file, offset, size) + # Read magic number + magic = read_data(file, BIG_ENDIAN) + if magic == FAT_MAGIC: + # Fat binaries contain nfat_arch Mach-O binaries + n_fat_arch = read_data(file, BIG_ENDIAN) + for _ in range(n_fat_arch): + # Read arch header + cpu_type, cpu_sub_type, offset, size, align = read_data(file, BIG_ENDIAN, 5) + do_file(file, offset, size) + elif magic == MH_MAGIC: + do_macho(file, 32, BIG_ENDIAN) + elif magic == MH_CIGAM: + do_macho(file, 32, LITTLE_ENDIAN) + elif magic == MH_MAGIC_64: + do_macho(file, 64, BIG_ENDIAN) + elif magic == MH_CIGAM_64: + do_macho(file, 64, LITTLE_ENDIAN) + + assert len(what) >= len(value) + + with open(at_path, "r+b") as f: + do_file(f) + + return mach_o_change diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/pypy/common.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/pypy/common.py new file mode 100644 index 0000000000000000000000000000000000000000..cc03b4293e77a645a992d8a55a3b54e21973043d --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/pypy/common.py @@ -0,0 +1,53 @@ +from __future__ import absolute_import, unicode_literals + +import abc + +from six import add_metaclass + +from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest, RefMust, RefWhen +from virtualenv.util.path import Path + +from ..via_global_self_do import ViaGlobalRefVirtualenvBuiltin + + +@add_metaclass(abc.ABCMeta) +class PyPy(ViaGlobalRefVirtualenvBuiltin): + @classmethod + def can_describe(cls, interpreter): + return interpreter.implementation == "PyPy" and super(PyPy, cls).can_describe(interpreter) + + @classmethod + def _executables(cls, interpreter): + host = Path(interpreter.system_executable) + targets = sorted("{}{}".format(name, PyPy.suffix) for name in cls.exe_names(interpreter)) + must = RefMust.COPY if interpreter.version_info.major == 2 else RefMust.NA + yield host, targets, must, RefWhen.ANY + + @classmethod + def exe_names(cls, interpreter): + return { + cls.exe_stem(), + "python", + "python{}".format(interpreter.version_info.major), + "python{}.{}".format(*interpreter.version_info), + } + + @classmethod + def sources(cls, interpreter): + for src in super(PyPy, cls).sources(interpreter): + yield src + for host in cls._add_shared_libs(interpreter): + yield PathRefToDest(host, dest=lambda self, s: self.bin_dir / s.name) + + @classmethod + def _add_shared_libs(cls, interpreter): + # https://bitbucket.org/pypy/pypy/issue/1922/future-proofing-virtualenv + python_dir = Path(interpreter.system_executable).resolve().parent + for libname in cls._shared_libs(): + src = python_dir / libname + if src.exists(): + yield src + + @classmethod + def _shared_libs(cls): + raise NotImplementedError diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/pypy/pypy2.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/pypy/pypy2.py new file mode 100644 index 0000000000000000000000000000000000000000..020000b3422178a6e758733c45d880adbb2b85a4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/pypy/pypy2.py @@ -0,0 +1,121 @@ +from __future__ import absolute_import, unicode_literals + +import abc +import logging +import os + +from six import add_metaclass + +from virtualenv.create.describe import PosixSupports, WindowsSupports +from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest +from virtualenv.util.path import Path + +from ..python2.python2 import Python2 +from .common import PyPy + + +@add_metaclass(abc.ABCMeta) +class PyPy2(PyPy, Python2): + """""" + + @classmethod + def exe_stem(cls): + return "pypy" + + @classmethod + def sources(cls, interpreter): + for src in super(PyPy2, cls).sources(interpreter): + yield src + # include folder needed on Python 2 as we don't have pyenv.cfg + host_include_marker = cls.host_include_marker(interpreter) + if host_include_marker.exists(): + yield PathRefToDest(host_include_marker.parent, dest=lambda self, _: self.include) + + @classmethod + def needs_stdlib_py_module(cls): + return True + + @classmethod + def host_include_marker(cls, interpreter): + return Path(interpreter.system_include) / "PyPy.h" + + @property + def include(self): + return self.dest / self.interpreter.distutils_install["headers"] + + @classmethod + def modules(cls): + # pypy2 uses some modules before the site.py loads, so we need to include these too + return super(PyPy2, cls).modules() + [ + "os", + "copy_reg", + "genericpath", + "linecache", + "stat", + "UserDict", + "warnings", + ] + + @property + def lib_pypy(self): + return self.dest / "lib_pypy" + + def ensure_directories(self): + dirs = super(PyPy2, self).ensure_directories() + dirs.add(self.lib_pypy) + host_include_marker = self.host_include_marker(self.interpreter) + if host_include_marker.exists(): + dirs.add(self.include.parent) + else: + logging.debug("no include folders as can't find include marker %s", host_include_marker) + return dirs + + @property + def skip_rewrite(self): + """ + PyPy2 built-in imports are handled by this path entry, don't overwrite to not disable it + see: https://github.com/pypa/virtualenv/issues/1652 + """ + return 'or path.endswith("lib_pypy{}__extensions__") # PyPy2 built-in import marker'.format(os.sep) + + +class PyPy2Posix(PyPy2, PosixSupports): + """PyPy 2 on POSIX""" + + @classmethod + def modules(cls): + return super(PyPy2Posix, cls).modules() + ["posixpath"] + + @classmethod + def _shared_libs(cls): + return ["libpypy-c.so", "libpypy-c.dylib"] + + @property + def lib(self): + return self.dest / "lib" + + @classmethod + def sources(cls, interpreter): + for src in super(PyPy2Posix, cls).sources(interpreter): + yield src + host_lib = Path(interpreter.system_prefix) / "lib" + if host_lib.exists(): + yield PathRefToDest(host_lib, dest=lambda self, _: self.lib) + + +class Pypy2Windows(PyPy2, WindowsSupports): + """PyPy 2 on Windows""" + + @classmethod + def modules(cls): + return super(Pypy2Windows, cls).modules() + ["ntpath"] + + @classmethod + def _shared_libs(cls): + return ["libpypy-c.dll"] + + @classmethod + def sources(cls, interpreter): + for src in super(Pypy2Windows, cls).sources(interpreter): + yield src + yield PathRefToDest(Path(interpreter.system_prefix) / "libs", dest=lambda self, s: self.dest / s.name) diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py new file mode 100644 index 0000000000000000000000000000000000000000..9588706786b9c06e13a73b596ae16741a85785aa --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, unicode_literals + +import abc + +from six import add_metaclass + +from virtualenv.create.describe import PosixSupports, Python3Supports, WindowsSupports +from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest +from virtualenv.util.path import Path + +from .common import PyPy + + +@add_metaclass(abc.ABCMeta) +class PyPy3(PyPy, Python3Supports): + @classmethod + def exe_stem(cls): + return "pypy3" + + @property + def stdlib(self): + """ + PyPy3 seems to respect sysconfig only for the host python... + virtual environments purelib is instead lib/pythonx.y + """ + return self.dest / "lib" / "python{}".format(self.interpreter.version_release_str) / "site-packages" + + @classmethod + def exe_names(cls, interpreter): + return super(PyPy3, cls).exe_names(interpreter) | {"pypy"} + + +class PyPy3Posix(PyPy3, PosixSupports): + """PyPy 2 on POSIX""" + + @classmethod + def _shared_libs(cls): + return ["libpypy3-c.so", "libpypy3-c.dylib"] + + def to_lib(self, src): + return self.dest / "lib" / src.name + + @classmethod + def sources(cls, interpreter): + for src in super(PyPy3Posix, cls).sources(interpreter): + yield src + host_lib = Path(interpreter.system_prefix) / "lib" + if host_lib.exists() and host_lib.is_dir(): + for path in host_lib.iterdir(): + yield PathRefToDest(path, dest=cls.to_lib) + + +class Pypy3Windows(PyPy3, WindowsSupports): + """PyPy 2 on Windows""" + + @property + def bin_dir(self): + """PyPy3 needs to fallback to pypy definition""" + return self.dest / "Scripts" + + @classmethod + def _shared_libs(cls): + return ["libpypy3-c.dll"] diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/python2/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/python2/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/python2/python2.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/python2/python2.py new file mode 100644 index 0000000000000000000000000000000000000000..cacd56ecfcda1673e8b0194f902e8ee9d5c26731 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/python2/python2.py @@ -0,0 +1,111 @@ +from __future__ import absolute_import, unicode_literals + +import abc +import json +import os + +from six import add_metaclass + +from virtualenv.create.describe import Python2Supports +from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest +from virtualenv.info import IS_ZIPAPP +from virtualenv.util.path import Path +from virtualenv.util.six import ensure_text +from virtualenv.util.zipapp import read as read_from_zipapp + +from ..via_global_self_do import ViaGlobalRefVirtualenvBuiltin + +HERE = Path(os.path.abspath(__file__)).parent + + +@add_metaclass(abc.ABCMeta) +class Python2(ViaGlobalRefVirtualenvBuiltin, Python2Supports): + def create(self): + """Perform operations needed to make the created environment work on Python 2""" + super(Python2, self).create() + # install a patched site-package, the default Python 2 site.py is not smart enough to understand pyvenv.cfg, + # so we inject a small shim that can do this, the location of this depends where it's on host + sys_std_plat = Path(self.interpreter.system_stdlib_platform) + site_py_in = ( + self.stdlib_platform + if ((sys_std_plat / "site.py").exists() or (sys_std_plat / "site.pyc").exists()) + else self.stdlib + ) + site_py = site_py_in / "site.py" + + custom_site = get_custom_site() + if IS_ZIPAPP: + custom_site_text = read_from_zipapp(custom_site) + else: + custom_site_text = custom_site.read_text() + expected = json.dumps([os.path.relpath(ensure_text(str(i)), ensure_text(str(site_py))) for i in self.libs]) + + custom_site_text = custom_site_text.replace("___EXPECTED_SITE_PACKAGES___", expected) + + reload_code = os.linesep.join(" {}".format(i) for i in self.reload_code.splitlines()).lstrip() + custom_site_text = custom_site_text.replace("# ___RELOAD_CODE___", reload_code) + + skip_rewrite = os.linesep.join(" {}".format(i) for i in self.skip_rewrite.splitlines()).lstrip() + custom_site_text = custom_site_text.replace("# ___SKIP_REWRITE____", skip_rewrite) + + site_py.write_text(custom_site_text) + + @property + def reload_code(self): + return 'reload(sys.modules["site"]) # noqa # call system site.py to setup import libraries' + + @property + def skip_rewrite(self): + return "" + + @classmethod + def sources(cls, interpreter): + for src in super(Python2, cls).sources(interpreter): + yield src + # install files needed to run site.py, either from stdlib or stdlib_platform, at least pyc, but both if exists + # if neither exists return the module file to trigger failure + mappings, needs_py_module = ( + cls.mappings(interpreter), + cls.needs_stdlib_py_module(), + ) + for req in cls.modules(): + module_file, to_module, module_exists = cls.from_stdlib(mappings, "{}.py".format(req)) + compiled_file, to_compiled, compiled_exists = cls.from_stdlib(mappings, "{}.pyc".format(req)) + if needs_py_module or module_exists or not compiled_exists: + yield PathRefToDest(module_file, dest=to_module) + if compiled_exists: + yield PathRefToDest(compiled_file, dest=to_compiled) + + @staticmethod + def from_stdlib(mappings, name): + for from_std, to_std in mappings: + src = from_std / name + if src.exists(): + return src, to_std, True + # if not exists, fallback to first in list + return mappings[0][0] / name, mappings[0][1], False + + @classmethod + def mappings(cls, interpreter): + mappings = [(Path(interpreter.system_stdlib_platform), cls.to_stdlib_platform)] + if interpreter.system_stdlib_platform != interpreter.system_stdlib: + mappings.append((Path(interpreter.system_stdlib), cls.to_stdlib)) + return mappings + + def to_stdlib(self, src): + return self.stdlib / src.name + + def to_stdlib_platform(self, src): + return self.stdlib_platform / src.name + + @classmethod + def needs_stdlib_py_module(cls): + raise NotImplementedError + + @classmethod + def modules(cls): + return [] + + +def get_custom_site(): + return HERE / "site.py" diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/python2/site.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/python2/site.py new file mode 100644 index 0000000000000000000000000000000000000000..85eee842aed849096b581705ad4fadff3e902e02 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/python2/site.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +""" +A simple shim module to fix up things on Python 2 only. + +Note: until we setup correctly the paths we can only import built-ins. +""" +import sys + + +def main(): + """Patch what needed, and invoke the original site.py""" + config = read_pyvenv() + sys.real_prefix = sys.base_prefix = config["base-prefix"] + sys.base_exec_prefix = config["base-exec-prefix"] + sys.base_executable = config["base-executable"] + global_site_package_enabled = config.get("include-system-site-packages", False) == "true" + rewrite_standard_library_sys_path() + disable_user_site_package() + load_host_site() + if global_site_package_enabled: + add_global_site_package() + + +def load_host_site(): + """trigger reload of site.py - now it will use the standard library instance that will take care of init""" + # we have a duality here, we generate the platform and pure library path based on what distutils.install specifies + # because this is what pip will be using; the host site.py though may contain it's own pattern for where the + # platform and pure library paths should exist + + # notably on Ubuntu there's a patch for getsitepackages to point to + # - prefix + local/lib/pythonx.y/dist-packages + # - prefix + lib/pythonx.y/dist-packages + # while distutils.install.cmd still points both of these to + # - prefix + lib/python2.7/site-packages + + # to facilitate when the two match, or not we first reload the site.py, now triggering the import of host site.py, + # as this will ensure that initialization code within host site.py runs + + here = __file__ # the distutils.install patterns will be injected relative to this site.py, save it here + + # ___RELOAD_CODE___ + + # and then if the distutils site packages are not on the sys.path we add them via add_site_dir; note we must add + # them by invoking add_site_dir to trigger the processing of pth files + import os + + site_packages = r""" + ___EXPECTED_SITE_PACKAGES___ + """ + import json + + add_site_dir = sys.modules["site"].addsitedir + for path in json.loads(site_packages): + full_path = os.path.abspath(os.path.join(here, path.encode("utf-8"))) + add_site_dir(full_path) + + +sep = "\\" if sys.platform == "win32" else "/" # no os module here yet - poor mans version + + +def read_pyvenv(): + """read pyvenv.cfg""" + config_file = "{}{}pyvenv.cfg".format(sys.prefix, sep) + with open(config_file) as file_handler: + lines = file_handler.readlines() + config = {} + for line in lines: + try: + split_at = line.index("=") + except ValueError: + continue # ignore bad/empty lines + else: + config[line[:split_at].strip()] = line[split_at + 1 :].strip() + return config + + +def rewrite_standard_library_sys_path(): + """Once this site file is loaded the standard library paths have already been set, fix them up""" + exe, prefix, exec_prefix = get_exe_prefixes(base=False) + base_exe, base_prefix, base_exec = get_exe_prefixes(base=True) + exe_dir = exe[: exe.rfind(sep)] + for at, path in enumerate(sys.path): + path = abs_path(path) # replace old sys prefix path starts with new + skip_rewrite = path == exe_dir # don't fix the current executable location, notably on Windows this gets added + skip_rewrite = skip_rewrite # ___SKIP_REWRITE____ + if not skip_rewrite: + sys.path[at] = map_path(path, base_exe, exe_dir, exec_prefix, base_prefix, prefix, base_exec) + + # the rewrite above may have changed elements from PYTHONPATH, revert these if on + if sys.flags.ignore_environment: + return + import os + + python_paths = [] + if "PYTHONPATH" in os.environ and os.environ["PYTHONPATH"]: + for path in os.environ["PYTHONPATH"].split(os.pathsep): + if path not in python_paths: + python_paths.append(path) + sys.path[: len(python_paths)] = python_paths + + +def get_exe_prefixes(base=False): + return tuple(abs_path(getattr(sys, ("base_" if base else "") + i)) for i in ("executable", "prefix", "exec_prefix")) + + +def abs_path(value): + values, keep = value.split(sep), [] + at = len(values) - 1 + while at >= 0: + if values[at] == "..": + at -= 1 + else: + keep.append(values[at]) + at -= 1 + return sep.join(keep[::-1]) + + +def map_path(path, base_executable, exe_dir, exec_prefix, base_prefix, prefix, base_exec_prefix): + if path_starts_with(path, exe_dir): + # content inside the exe folder needs to remap to original executables folder + orig_exe_folder = base_executable[: base_executable.rfind(sep)] + return "{}{}".format(orig_exe_folder, path[len(exe_dir) :]) + elif path_starts_with(path, prefix): + return "{}{}".format(base_prefix, path[len(prefix) :]) + elif path_starts_with(path, exec_prefix): + return "{}{}".format(base_exec_prefix, path[len(exec_prefix) :]) + return path + + +def path_starts_with(directory, value): + return directory.startswith(value if value[-1] == sep else value + sep) + + +def disable_user_site_package(): + """Flip the switch on enable user site package""" + # sys.flags is a c-extension type, so we cannot monkeypatch it, replace it with a python class to flip it + sys.original_flags = sys.flags + + class Flags(object): + def __init__(self): + self.__dict__ = {key: getattr(sys.flags, key) for key in dir(sys.flags) if not key.startswith("_")} + + sys.flags = Flags() + sys.flags.no_user_site = 1 + + +def add_global_site_package(): + """add the global site package""" + import site + + # add user site package + sys.flags = sys.original_flags # restore original + site.ENABLE_USER_SITE = None # reset user site check + # add the global site package to the path - use new prefix and delegate to site.py + orig_prefixes = None + try: + orig_prefixes = site.PREFIXES + site.PREFIXES = [sys.base_prefix, sys.base_exec_prefix] + site.main() + finally: + site.PREFIXES = orig_prefixes + + +main() diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/ref.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/ref.py new file mode 100644 index 0000000000000000000000000000000000000000..69f243bf98496c6341b16182f9796e6b6c882996 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/ref.py @@ -0,0 +1,172 @@ +""" +Virtual environments in the traditional sense are built as reference to the host python. This file allows declarative +references to elements on the file system, allowing our system to automatically detect what modes it can support given +the constraints: e.g. can the file system symlink, can the files be read, executed, etc. +""" +from __future__ import absolute_import, unicode_literals + +import os +from abc import ABCMeta, abstractmethod +from collections import OrderedDict +from stat import S_IXGRP, S_IXOTH, S_IXUSR + +from six import add_metaclass + +from virtualenv.info import fs_is_case_sensitive, fs_supports_symlink +from virtualenv.util.path import copy, make_exe, symlink +from virtualenv.util.six import ensure_text + + +class RefMust(object): + NA = "NA" + COPY = "copy" + SYMLINK = "symlink" + + +class RefWhen(object): + ANY = "ANY" + COPY = "copy" + SYMLINK = "symlink" + + +@add_metaclass(ABCMeta) +class PathRef(object): + """Base class that checks if a file reference can be symlink/copied""" + + FS_SUPPORTS_SYMLINK = fs_supports_symlink() + FS_CASE_SENSITIVE = fs_is_case_sensitive() + + def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY): + self.must = must + self.when = when + self.src = src + try: + self.exists = src.exists() + except OSError: + self.exists = False + self._can_read = None if self.exists else False + self._can_copy = None if self.exists else False + self._can_symlink = None if self.exists else False + + def __repr__(self): + return "{}(src={})".format(self.__class__.__name__, self.src) + + @property + def can_read(self): + if self._can_read is None: + if self.src.is_file(): + try: + with self.src.open("rb"): + self._can_read = True + except OSError: + self._can_read = False + else: + self._can_read = os.access(ensure_text(str(self.src)), os.R_OK) + return self._can_read + + @property + def can_copy(self): + if self._can_copy is None: + if self.must == RefMust.SYMLINK: + self._can_copy = self.can_symlink + else: + self._can_copy = self.can_read + return self._can_copy + + @property + def can_symlink(self): + if self._can_symlink is None: + if self.must == RefMust.COPY: + self._can_symlink = self.can_copy + else: + self._can_symlink = self.FS_SUPPORTS_SYMLINK and self.can_read + return self._can_symlink + + @abstractmethod + def run(self, creator, symlinks): + raise NotImplementedError + + def method(self, symlinks): + if self.must == RefMust.SYMLINK: + return symlink + if self.must == RefMust.COPY: + return copy + return symlink if symlinks else copy + + +@add_metaclass(ABCMeta) +class ExePathRef(PathRef): + """Base class that checks if a executable can be references via symlink/copy""" + + def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY): + super(ExePathRef, self).__init__(src, must, when) + self._can_run = None + + @property + def can_symlink(self): + if self.FS_SUPPORTS_SYMLINK: + return self.can_run + return False + + @property + def can_run(self): + if self._can_run is None: + mode = self.src.stat().st_mode + for key in [S_IXUSR, S_IXGRP, S_IXOTH]: + if mode & key: + self._can_run = True + break + else: + self._can_run = False + return self._can_run + + +class PathRefToDest(PathRef): + """Link a path on the file system""" + + def __init__(self, src, dest, must=RefMust.NA, when=RefWhen.ANY): + super(PathRefToDest, self).__init__(src, must, when) + self.dest = dest + + def run(self, creator, symlinks): + dest = self.dest(creator, self.src) + method = self.method(symlinks) + dest_iterable = dest if isinstance(dest, list) else (dest,) + if not dest.parent.exists(): + dest.parent.mkdir(parents=True, exist_ok=True) + for dst in dest_iterable: + method(self.src, dst) + + +class ExePathRefToDest(PathRefToDest, ExePathRef): + """Link a exe path on the file system""" + + def __init__(self, src, targets, dest, must=RefMust.NA, when=RefWhen.ANY): + ExePathRef.__init__(self, src, must, when) + PathRefToDest.__init__(self, src, dest, must, when) + if not self.FS_CASE_SENSITIVE: + targets = list(OrderedDict((i.lower(), None) for i in targets).keys()) + self.base = targets[0] + self.aliases = targets[1:] + self.dest = dest + + def run(self, creator, symlinks): + bin_dir = self.dest(creator, self.src).parent + dest = bin_dir / self.base + method = self.method(symlinks) + method(self.src, dest) + if not symlinks: + make_exe(dest) + for extra in self.aliases: + link_file = bin_dir / extra + if link_file.exists(): + link_file.unlink() + if symlinks: + link_file.symlink_to(self.base) + else: + copy(self.src, link_file) + if not symlinks: + make_exe(link_file) + + def __repr__(self): + return "{}(src={}, alias={})".format(self.__class__.__name__, self.src, self.aliases) diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/via_global_self_do.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/via_global_self_do.py new file mode 100644 index 0000000000000000000000000000000000000000..863ae16e1d14a1fb4836db1a710596466a9fd5b0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/builtin/via_global_self_do.py @@ -0,0 +1,114 @@ +from __future__ import absolute_import, unicode_literals + +from abc import ABCMeta + +from six import add_metaclass + +from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, RefMust, RefWhen +from virtualenv.util.path import ensure_dir + +from ..api import ViaGlobalRefApi, ViaGlobalRefMeta +from .builtin_way import VirtualenvBuiltin + + +class BuiltinViaGlobalRefMeta(ViaGlobalRefMeta): + def __init__(self): + super(BuiltinViaGlobalRefMeta, self).__init__() + self.sources = [] + + +@add_metaclass(ABCMeta) +class ViaGlobalRefVirtualenvBuiltin(ViaGlobalRefApi, VirtualenvBuiltin): + def __init__(self, options, interpreter): + super(ViaGlobalRefVirtualenvBuiltin, self).__init__(options, interpreter) + self._sources = getattr(options.meta, "sources", None) # if we're created as a describer this might be missing + + @classmethod + def can_create(cls, interpreter): + """By default all built-in methods assume that if we can describe it we can create it""" + # first we must be able to describe it + if not cls.can_describe(interpreter): + return None + meta = cls.setup_meta(interpreter) + if meta is not None and meta: + cls._sources_can_be_applied(interpreter, meta) + return meta + + @classmethod + def _sources_can_be_applied(cls, interpreter, meta): + for src in cls.sources(interpreter): + if src.exists: + if meta.can_copy and not src.can_copy: + meta.copy_error = "cannot copy {}".format(src) + if meta.can_symlink and not src.can_symlink: + meta.symlink_error = "cannot symlink {}".format(src) + else: + msg = "missing required file {}".format(src) + if src.when == RefMust.NA: + meta.error = msg + elif src.when == RefMust.COPY: + meta.copy_error = msg + elif src.when == RefMust.SYMLINK: + meta.symlink_error = msg + if not meta.can_copy and not meta.can_symlink: + meta.error = "neither copy or symlink supported, copy: {} symlink: {}".format( + meta.copy_error, + meta.symlink_error, + ) + if meta.error: + break + meta.sources.append(src) + + @classmethod + def setup_meta(cls, interpreter): + return BuiltinViaGlobalRefMeta() + + @classmethod + def sources(cls, interpreter): + for host_exe, targets, must, when in cls._executables(interpreter): + yield ExePathRefToDest(host_exe, dest=cls.to_bin, targets=targets, must=must, when=when) + + def to_bin(self, src): + return self.bin_dir / src.name + + @classmethod + def _executables(cls, interpreter): + raise NotImplementedError + + def create(self): + dirs = self.ensure_directories() + for directory in list(dirs): + if any(i for i in dirs if i is not directory and directory.parts == i.parts[: len(directory.parts)]): + dirs.remove(directory) + for directory in sorted(dirs): + ensure_dir(directory) + + self.set_pyenv_cfg() + self.pyenv_cfg.write() + true_system_site = self.enable_system_site_package + try: + self.enable_system_site_package = False + for src in self._sources: + if ( + src.when == RefWhen.ANY + or (src.when == RefWhen.SYMLINK and self.symlinks is True) + or (src.when == RefWhen.COPY and self.symlinks is False) + ): + src.run(self, self.symlinks) + finally: + if true_system_site != self.enable_system_site_package: + self.enable_system_site_package = true_system_site + super(ViaGlobalRefVirtualenvBuiltin, self).create() + + def ensure_directories(self): + return {self.dest, self.bin_dir, self.script_dir, self.stdlib} | set(self.libs) + + def set_pyenv_cfg(self): + """ + We directly inject the base prefix and base exec prefix to avoid site.py needing to discover these + from home (which usually is done within the interpreter itself) + """ + super(ViaGlobalRefVirtualenvBuiltin, self).set_pyenv_cfg() + self.pyenv_cfg["base-prefix"] = self.interpreter.system_prefix + self.pyenv_cfg["base-exec-prefix"] = self.interpreter.system_exec_prefix + self.pyenv_cfg["base-executable"] = self.interpreter.system_executable diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/store.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/store.py new file mode 100644 index 0000000000000000000000000000000000000000..134a535859fe8cf732442508ba8e0cd3c592de31 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/store.py @@ -0,0 +1,26 @@ +from __future__ import absolute_import, unicode_literals + +from virtualenv.util.path import Path + + +def handle_store_python(meta, interpreter): + if is_store_python(interpreter): + meta.symlink_error = "Windows Store Python does not support virtual environments via symlink" + return meta + + +def is_store_python(interpreter): + parts = Path(interpreter.system_executable).parts + return ( + len(parts) > 4 + and parts[-4] == "Microsoft" + and parts[-3] == "WindowsApps" + and parts[-2].startswith("PythonSoftwareFoundation.Python.3.") + and parts[-1].startswith("python") + ) + + +__all__ = ( + "handle_store_python", + "is_store_python", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/venv.py b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/venv.py new file mode 100644 index 0000000000000000000000000000000000000000..aaa67947f157715f14d531f4f189efa8c4aec333 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/create/via_global_ref/venv.py @@ -0,0 +1,83 @@ +from __future__ import absolute_import, unicode_literals + +import logging +from copy import copy + +from virtualenv.create.via_global_ref.store import handle_store_python +from virtualenv.discovery.py_info import PythonInfo +from virtualenv.util.error import ProcessCallFailed +from virtualenv.util.path import ensure_dir +from virtualenv.util.subprocess import run_cmd + +from .api import ViaGlobalRefApi, ViaGlobalRefMeta + + +class Venv(ViaGlobalRefApi): + def __init__(self, options, interpreter): + self.describe = options.describe + super(Venv, self).__init__(options, interpreter) + self.can_be_inline = ( + interpreter is PythonInfo.current() and interpreter.executable == interpreter.system_executable + ) + self._context = None + + def _args(self): + return super(Venv, self)._args() + ([("describe", self.describe.__class__.__name__)] if self.describe else []) + + @classmethod + def can_create(cls, interpreter): + if interpreter.has_venv: + meta = ViaGlobalRefMeta() + if interpreter.platform == "win32" and interpreter.version_info.major == 3: + meta = handle_store_python(meta, interpreter) + return meta + return None + + def create(self): + if self.can_be_inline: + self.create_inline() + else: + self.create_via_sub_process() + for lib in self.libs: + ensure_dir(lib) + super(Venv, self).create() + + def create_inline(self): + from venv import EnvBuilder + + builder = EnvBuilder( + system_site_packages=self.enable_system_site_package, + clear=False, + symlinks=self.symlinks, + with_pip=False, + ) + builder.create(str(self.dest)) + + def create_via_sub_process(self): + cmd = self.get_host_create_cmd() + logging.info("using host built-in venv to create via %s", " ".join(cmd)) + code, out, err = run_cmd(cmd) + if code != 0: + raise ProcessCallFailed(code, out, err, cmd) + + def get_host_create_cmd(self): + cmd = [self.interpreter.system_executable, "-m", "venv", "--without-pip"] + if self.enable_system_site_package: + cmd.append("--system-site-packages") + cmd.append("--symlinks" if self.symlinks else "--copies") + cmd.append(str(self.dest)) + return cmd + + def set_pyenv_cfg(self): + # prefer venv options over ours, but keep our extra + venv_content = copy(self.pyenv_cfg.refresh()) + super(Venv, self).set_pyenv_cfg() + self.pyenv_cfg.update(venv_content) + + def __getattribute__(self, item): + describe = object.__getattribute__(self, "describe") + if describe is not None and hasattr(describe, item): + element = getattr(describe, item) + if not callable(element) or item in ("script",): + return element + return object.__getattribute__(self, item) diff --git a/robot/lib/python3.8/site-packages/virtualenv/discovery/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/discovery/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..01e6d4f49d5f5d8103b5120746b16d0cc56add77 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/discovery/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import, unicode_literals diff --git a/robot/lib/python3.8/site-packages/virtualenv/discovery/builtin.py b/robot/lib/python3.8/site-packages/virtualenv/discovery/builtin.py new file mode 100644 index 0000000000000000000000000000000000000000..70ffbced0c603f060bd743697e373e549a77c7a3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/discovery/builtin.py @@ -0,0 +1,183 @@ +from __future__ import absolute_import, unicode_literals + +import logging +import os +import sys + +from virtualenv.info import IS_WIN +from virtualenv.util.six import ensure_str, ensure_text + +from .discover import Discover +from .py_info import PythonInfo +from .py_spec import PythonSpec + + +class Builtin(Discover): + def __init__(self, options): + super(Builtin, self).__init__(options) + self.python_spec = options.python if options.python else [sys.executable] + self.app_data = options.app_data + self.try_first_with = options.try_first_with + + @classmethod + def add_parser_arguments(cls, parser): + parser.add_argument( + "-p", + "--python", + dest="python", + metavar="py", + type=str, + action="append", + default=[], + help="interpreter based on what to create environment (path/identifier) " + "- by default use the interpreter where the tool is installed - first found wins", + ) + parser.add_argument( + "--try-first-with", + dest="try_first_with", + metavar="py_exe", + type=str, + action="append", + default=[], + help="try first these interpreters before starting the discovery", + ) + + def run(self): + for python_spec in self.python_spec: + result = get_interpreter(python_spec, self.try_first_with, self.app_data) + if result is not None: + return result + return None + + def __repr__(self): + return ensure_str(self.__unicode__()) + + def __unicode__(self): + spec = self.python_spec[0] if len(self.python_spec) == 1 else self.python_spec + return "{} discover of python_spec={!r}".format(self.__class__.__name__, spec) + + +def get_interpreter(key, try_first_with, app_data=None): + spec = PythonSpec.from_string_spec(key) + logging.info("find interpreter for spec %r", spec) + proposed_paths = set() + for interpreter, impl_must_match in propose_interpreters(spec, try_first_with, app_data): + key = interpreter.system_executable, impl_must_match + if key in proposed_paths: + continue + logging.info("proposed %s", interpreter) + if interpreter.satisfies(spec, impl_must_match): + logging.debug("accepted %s", interpreter) + return interpreter + proposed_paths.add(key) + + +def propose_interpreters(spec, try_first_with, app_data): + # 0. try with first + for py_exe in try_first_with: + path = os.path.abspath(py_exe) + try: + os.lstat(path) # Windows Store Python does not work with os.path.exists, but does for os.lstat + except OSError: + pass + else: + yield PythonInfo.from_exe(os.path.abspath(path), app_data), True + + # 1. if it's a path and exists + if spec.path is not None: + try: + os.lstat(spec.path) # Windows Store Python does not work with os.path.exists, but does for os.lstat + except OSError: + if spec.is_abs: + raise + else: + yield PythonInfo.from_exe(os.path.abspath(spec.path), app_data), True + if spec.is_abs: + return + else: + # 2. otherwise try with the current + yield PythonInfo.current_system(app_data), True + + # 3. otherwise fallback to platform default logic + if IS_WIN: + from .windows import propose_interpreters + + for interpreter in propose_interpreters(spec, app_data): + yield interpreter, True + # finally just find on path, the path order matters (as the candidates are less easy to control by end user) + paths = get_paths() + tested_exes = set() + for pos, path in enumerate(paths): + path = ensure_text(path) + logging.debug(LazyPathDump(pos, path)) + for candidate, match in possible_specs(spec): + found = check_path(candidate, path) + if found is not None: + exe = os.path.abspath(found) + if exe not in tested_exes: + tested_exes.add(exe) + interpreter = PathPythonInfo.from_exe(exe, app_data, raise_on_error=False) + if interpreter is not None: + yield interpreter, match + + +def get_paths(): + path = os.environ.get(str("PATH"), None) + if path is None: + try: + path = os.confstr("CS_PATH") + except (AttributeError, ValueError): + path = os.defpath + if not path: + paths = [] + else: + paths = [p for p in path.split(os.pathsep) if os.path.exists(p)] + return paths + + +class LazyPathDump(object): + def __init__(self, pos, path): + self.pos = pos + self.path = path + + def __repr__(self): + return ensure_str(self.__unicode__()) + + def __unicode__(self): + content = "discover PATH[{}]={}".format(self.pos, self.path) + if os.environ.get(str("_VIRTUALENV_DEBUG")): # this is the over the board debug + content += " with =>" + for file_name in os.listdir(self.path): + try: + file_path = os.path.join(self.path, file_name) + if os.path.isdir(file_path) or not os.access(file_path, os.X_OK): + continue + except OSError: + pass + content += " " + content += file_name + return content + + +def check_path(candidate, path): + _, ext = os.path.splitext(candidate) + if sys.platform == "win32" and ext != ".exe": + candidate = candidate + ".exe" + if os.path.isfile(candidate): + return candidate + candidate = os.path.join(path, candidate) + if os.path.isfile(candidate): + return candidate + return None + + +def possible_specs(spec): + # 4. then maybe it's something exact on PATH - if it was direct lookup implementation no longer counts + yield spec.str_spec, False + # 5. or from the spec we can deduce a name on path that matches + for exe, match in spec.generate_names(): + yield exe, match + + +class PathPythonInfo(PythonInfo): + """""" diff --git a/robot/lib/python3.8/site-packages/virtualenv/discovery/cached_py_info.py b/robot/lib/python3.8/site-packages/virtualenv/discovery/cached_py_info.py new file mode 100644 index 0000000000000000000000000000000000000000..ce79ef14b1f8bc9fea598f2dbc355602a23eb3fc --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/discovery/cached_py_info.py @@ -0,0 +1,148 @@ +""" + +We acquire the python information by running an interrogation script via subprocess trigger. This operation is not +cheap, especially not on Windows. To not have to pay this hefty cost every time we apply multiple levels of +caching. +""" +from __future__ import absolute_import, unicode_literals + +import logging +import os +import pipes +import sys +from collections import OrderedDict + +from virtualenv.app_data import AppDataDisabled +from virtualenv.discovery.py_info import PythonInfo +from virtualenv.info import PY2 +from virtualenv.util.path import Path +from virtualenv.util.six import ensure_text +from virtualenv.util.subprocess import Popen, subprocess + +_CACHE = OrderedDict() +_CACHE[Path(sys.executable)] = PythonInfo() + + +def from_exe(cls, app_data, exe, raise_on_error=True, ignore_cache=False): + """""" + result = _get_from_cache(cls, app_data, exe, ignore_cache=ignore_cache) + if isinstance(result, Exception): + if raise_on_error: + raise result + else: + logging.info("%s", str(result)) + result = None + return result + + +def _get_from_cache(cls, app_data, exe, ignore_cache=True): + # note here we cannot resolve symlinks, as the symlink may trigger different prefix information if there's a + # pyenv.cfg somewhere alongside on python3.4+ + exe_path = Path(exe) + if not ignore_cache and exe_path in _CACHE: # check in the in-memory cache + result = _CACHE[exe_path] + else: # otherwise go through the app data cache + py_info = _get_via_file_cache(cls, app_data, exe_path, exe) + result = _CACHE[exe_path] = py_info + # independent if it was from the file or in-memory cache fix the original executable location + if isinstance(result, PythonInfo): + result.executable = exe + return result + + +def _get_via_file_cache(cls, app_data, path, exe): + path_text = ensure_text(str(path)) + try: + path_modified = path.stat().st_mtime + except OSError: + path_modified = -1 + if app_data is None: + app_data = AppDataDisabled() + py_info, py_info_store = None, app_data.py_info(path) + with py_info_store.locked(): + if py_info_store.exists(): # if exists and matches load + data = py_info_store.read() + of_path, of_st_mtime, of_content = data["path"], data["st_mtime"], data["content"] + if of_path == path_text and of_st_mtime == path_modified: + py_info = cls._from_dict({k: v for k, v in of_content.items()}) + else: + py_info_store.remove() + if py_info is None: # if not loaded run and save + failure, py_info = _run_subprocess(cls, exe, app_data) + if failure is None: + data = {"st_mtime": path_modified, "path": path_text, "content": py_info._to_dict()} + py_info_store.write(data) + else: + py_info = failure + return py_info + + +def _run_subprocess(cls, exe, app_data): + py_info_script = Path(os.path.abspath(__file__)).parent / "py_info.py" + with app_data.ensure_extracted(py_info_script) as py_info_script: + cmd = [exe, str(py_info_script)] + # prevent sys.prefix from leaking into the child process - see https://bugs.python.org/issue22490 + env = os.environ.copy() + env.pop("__PYVENV_LAUNCHER__", None) + logging.debug("get interpreter info via cmd: %s", LogCmd(cmd)) + try: + process = Popen( + cmd, + universal_newlines=True, + stdin=subprocess.PIPE, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + env=env, + ) + out, err = process.communicate() + code = process.returncode + except OSError as os_error: + out, err, code = "", os_error.strerror, os_error.errno + result, failure = None, None + if code == 0: + result = cls._from_json(out) + result.executable = exe # keep original executable as this may contain initialization code + else: + msg = "failed to query {} with code {}{}{}".format( + exe, + code, + " out: {!r}".format(out) if out else "", + " err: {!r}".format(err) if err else "", + ) + failure = RuntimeError(msg) + return failure, result + + +class LogCmd(object): + def __init__(self, cmd, env=None): + self.cmd = cmd + self.env = env + + def __repr__(self): + def e(v): + return v.decode("utf-8") if isinstance(v, bytes) else v + + cmd_repr = e(" ").join(pipes.quote(e(c)) for c in self.cmd) + if self.env is not None: + cmd_repr += e(" env of {!r}").format(self.env) + if PY2: + return cmd_repr.encode("utf-8") + return cmd_repr + + def __unicode__(self): + raw = repr(self) + if PY2: + return raw.decode("utf-8") + return raw + + +def clear(app_data): + app_data.py_info_clear() + _CACHE.clear() + + +___all___ = ( + "from_exe", + "clear", + "LogCmd", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/discovery/discover.py b/robot/lib/python3.8/site-packages/virtualenv/discovery/discover.py new file mode 100644 index 0000000000000000000000000000000000000000..93c3ea7ad7443ef21a1147865d9e593864caf9e5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/discovery/discover.py @@ -0,0 +1,46 @@ +from __future__ import absolute_import, unicode_literals + +from abc import ABCMeta, abstractmethod + +from six import add_metaclass + + +@add_metaclass(ABCMeta) +class Discover(object): + """Discover and provide the requested Python interpreter""" + + @classmethod + def add_parser_arguments(cls, parser): + """Add CLI arguments for this discovery mechanisms. + + :param parser: the CLI parser + """ + raise NotImplementedError + + # noinspection PyUnusedLocal + def __init__(self, options): + """Create a new discovery mechanism. + + :param options: the parsed options as defined within :meth:`add_parser_arguments` + """ + self._has_run = False + self._interpreter = None + + @abstractmethod + def run(self): + """Discovers an interpreter. + + + :return: the interpreter ready to use for virtual environment creation + """ + raise NotImplementedError + + @property + def interpreter(self): + """ + :return: the interpreter as returned by :meth:`run`, cached + """ + if self._has_run is False: + self._interpreter = self.run() + self._has_run = True + return self._interpreter diff --git a/robot/lib/python3.8/site-packages/virtualenv/discovery/py_info.py b/robot/lib/python3.8/site-packages/virtualenv/discovery/py_info.py new file mode 100644 index 0000000000000000000000000000000000000000..46b51df1b3e693f18167604db180fa3249c01f49 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/discovery/py_info.py @@ -0,0 +1,490 @@ +""" +The PythonInfo contains information about a concrete instance of a Python interpreter + +Note: this file is also used to query target interpreters, so can only use standard library methods +""" +from __future__ import absolute_import, print_function + +import json +import logging +import os +import platform +import re +import sys +import sysconfig +from collections import OrderedDict, namedtuple +from distutils import dist +from distutils.command.install import SCHEME_KEYS +from string import digits + +VersionInfo = namedtuple("VersionInfo", ["major", "minor", "micro", "releaselevel", "serial"]) + + +def _get_path_extensions(): + return list(OrderedDict.fromkeys([""] + os.environ.get("PATHEXT", "").lower().split(os.pathsep))) + + +EXTENSIONS = _get_path_extensions() +_CONF_VAR_RE = re.compile(r"\{\w+\}") + + +class PythonInfo(object): + """Contains information for a Python interpreter""" + + def __init__(self): + def u(v): + return v.decode("utf-8") if isinstance(v, bytes) else v + + def abs_path(v): + return None if v is None else os.path.abspath(v) # unroll relative elements from path (e.g. ..) + + # qualifies the python + self.platform = u(sys.platform) + self.implementation = u(platform.python_implementation()) + if self.implementation == "PyPy": + self.pypy_version_info = tuple(u(i) for i in sys.pypy_version_info) + + # this is a tuple in earlier, struct later, unify to our own named tuple + self.version_info = VersionInfo(*list(u(i) for i in sys.version_info)) + self.architecture = 64 if sys.maxsize > 2 ** 32 else 32 + + self.version = u(sys.version) + self.os = u(os.name) + + # information about the prefix - determines python home + self.prefix = u(abs_path(getattr(sys, "prefix", None))) # prefix we think + self.base_prefix = u(abs_path(getattr(sys, "base_prefix", None))) # venv + self.real_prefix = u(abs_path(getattr(sys, "real_prefix", None))) # old virtualenv + + # information about the exec prefix - dynamic stdlib modules + self.base_exec_prefix = u(abs_path(getattr(sys, "base_exec_prefix", None))) + self.exec_prefix = u(abs_path(getattr(sys, "exec_prefix", None))) + + self.executable = u(abs_path(sys.executable)) # the executable we were invoked via + self.original_executable = u(abs_path(self.executable)) # the executable as known by the interpreter + self.system_executable = self._fast_get_system_executable() # the executable we are based of (if available) + + try: + __import__("venv") + has = True + except ImportError: + has = False + self.has_venv = has + self.path = [u(i) for i in sys.path] + self.file_system_encoding = u(sys.getfilesystemencoding()) + self.stdout_encoding = u(getattr(sys.stdout, "encoding", None)) + + self.sysconfig_paths = {u(i): u(sysconfig.get_path(i, expand=False)) for i in sysconfig.get_path_names()} + # https://bugs.python.org/issue22199 + makefile = getattr(sysconfig, "get_makefile_filename", getattr(sysconfig, "_get_makefile_filename", None)) + self.sysconfig = { + u(k): u(v) + for k, v in [ + # a list of content to store from sysconfig + ("makefile_filename", makefile()), + ] + if k is not None + } + + config_var_keys = set() + for element in self.sysconfig_paths.values(): + for k in _CONF_VAR_RE.findall(element): + config_var_keys.add(u(k[1:-1])) + config_var_keys.add("PYTHONFRAMEWORK") + + self.sysconfig_vars = {u(i): u(sysconfig.get_config_var(i) or "") for i in config_var_keys} + if self.implementation == "PyPy" and sys.version_info.major == 2: + self.sysconfig_vars[u"implementation_lower"] = u"python" + + self.distutils_install = {u(k): u(v) for k, v in self._distutils_install().items()} + confs = {k: (self.system_prefix if v.startswith(self.prefix) else v) for k, v in self.sysconfig_vars.items()} + self.system_stdlib = self.sysconfig_path("stdlib", confs) + self.system_stdlib_platform = self.sysconfig_path("platstdlib", confs) + self.max_size = getattr(sys, "maxsize", getattr(sys, "maxint", None)) + self._creators = None + + def _fast_get_system_executable(self): + """Try to get the system executable by just looking at properties""" + if self.real_prefix or ( + self.base_prefix is not None and self.base_prefix != self.prefix + ): # if this is a virtual environment + if self.real_prefix is None: + base_executable = getattr(sys, "_base_executable", None) # some platforms may set this to help us + if base_executable is not None: # use the saved system executable if present + if sys.executable != base_executable: # we know we're in a virtual environment, cannot be us + return base_executable + return None # in this case we just can't tell easily without poking around FS and calling them, bail + # if we're not in a virtual environment, this is already a system python, so return the original executable + # note we must choose the original and not the pure executable as shim scripts might throw us off + return self.original_executable + + @staticmethod + def _distutils_install(): + # follow https://github.com/pypa/pip/blob/main/src/pip/_internal/locations.py#L95 + # note here we don't import Distribution directly to allow setuptools to patch it + d = dist.Distribution({"script_args": "--no-user-cfg"}) # conf files not parsed so they do not hijack paths + if hasattr(sys, "_framework"): + sys._framework = None # disable macOS static paths for framework + i = d.get_command_obj("install", create=True) + i.prefix = os.sep # paths generated are relative to prefix that contains the path sep, this makes it relative + i.finalize_options() + result = {key: (getattr(i, "install_{}".format(key))[1:]).lstrip(os.sep) for key in SCHEME_KEYS} + return result + + @property + def version_str(self): + return ".".join(str(i) for i in self.version_info[0:3]) + + @property + def version_release_str(self): + return ".".join(str(i) for i in self.version_info[0:2]) + + @property + def python_name(self): + version_info = self.version_info + return "python{}.{}".format(version_info.major, version_info.minor) + + @property + def is_old_virtualenv(self): + return self.real_prefix is not None + + @property + def is_venv(self): + return self.base_prefix is not None and self.version_info.major == 3 + + def sysconfig_path(self, key, config_var=None, sep=os.sep): + pattern = self.sysconfig_paths[key] + if config_var is None: + config_var = self.sysconfig_vars + else: + base = {k: v for k, v in self.sysconfig_vars.items()} + base.update(config_var) + config_var = base + return pattern.format(**config_var).replace(u"/", sep) + + def creators(self, refresh=False): + if self._creators is None or refresh is True: + from virtualenv.run.plugin.creators import CreatorSelector + + self._creators = CreatorSelector.for_interpreter(self) + return self._creators + + @property + def system_include(self): + path = self.sysconfig_path( + "include", + {k: (self.system_prefix if v.startswith(self.prefix) else v) for k, v in self.sysconfig_vars.items()}, + ) + if not os.path.exists(path): # some broken packaging don't respect the sysconfig, fallback to distutils path + # the pattern include the distribution name too at the end, remove that via the parent call + fallback = os.path.join(self.prefix, os.path.dirname(self.distutils_install["headers"])) + if os.path.exists(fallback): + path = fallback + return path + + @property + def system_prefix(self): + return self.real_prefix or self.base_prefix or self.prefix + + @property + def system_exec_prefix(self): + return self.real_prefix or self.base_exec_prefix or self.exec_prefix + + def __unicode__(self): + content = repr(self) + if sys.version_info == 2: + content = content.decode("utf-8") + return content + + def __repr__(self): + return "{}({!r})".format( + self.__class__.__name__, + {k: v for k, v in self.__dict__.items() if not k.startswith("_")}, + ) + + def __str__(self): + content = "{}({})".format( + self.__class__.__name__, + ", ".join( + "{}={}".format(k, v) + for k, v in ( + ("spec", self.spec), + ( + "system" + if self.system_executable is not None and self.system_executable != self.executable + else None, + self.system_executable, + ), + ( + "original" + if ( + self.original_executable != self.system_executable + and self.original_executable != self.executable + ) + else None, + self.original_executable, + ), + ("exe", self.executable), + ("platform", self.platform), + ("version", repr(self.version)), + ("encoding_fs_io", "{}-{}".format(self.file_system_encoding, self.stdout_encoding)), + ) + if k is not None + ), + ) + return content + + @property + def spec(self): + return "{}{}-{}".format(self.implementation, ".".join(str(i) for i in self.version_info), self.architecture) + + @classmethod + def clear_cache(cls, app_data): + # this method is not used by itself, so here and called functions can import stuff locally + from virtualenv.discovery.cached_py_info import clear + + clear(app_data) + cls._cache_exe_discovery.clear() + + def satisfies(self, spec, impl_must_match): + """check if a given specification can be satisfied by the this python interpreter instance""" + if spec.path: + if self.executable == os.path.abspath(spec.path): + return True # if the path is a our own executable path we're done + if not spec.is_abs: + # if path set, and is not our original executable name, this does not match + basename = os.path.basename(self.original_executable) + spec_path = spec.path + if sys.platform == "win32": + basename, suffix = os.path.splitext(basename) + if spec_path.endswith(suffix): + spec_path = spec_path[: -len(suffix)] + if basename != spec_path: + return False + + if impl_must_match: + if spec.implementation is not None and spec.implementation.lower() != self.implementation.lower(): + return False + + if spec.architecture is not None and spec.architecture != self.architecture: + return False + + for our, req in zip(self.version_info[0:3], (spec.major, spec.minor, spec.micro)): + if req is not None and our is not None and our != req: + return False + return True + + _current_system = None + _current = None + + @classmethod + def current(cls, app_data=None): + """ + This locates the current host interpreter information. This might be different than what we run into in case + the host python has been upgraded from underneath us. + """ + if cls._current is None: + cls._current = cls.from_exe(sys.executable, app_data, raise_on_error=True, resolve_to_host=False) + return cls._current + + @classmethod + def current_system(cls, app_data=None): + """ + This locates the current host interpreter information. This might be different than what we run into in case + the host python has been upgraded from underneath us. + """ + if cls._current_system is None: + cls._current_system = cls.from_exe(sys.executable, app_data, raise_on_error=True, resolve_to_host=True) + return cls._current_system + + def _to_json(self): + # don't save calculated paths, as these are non primitive types + return json.dumps(self._to_dict(), indent=2) + + def _to_dict(self): + data = {var: (getattr(self, var) if var not in ("_creators",) else None) for var in vars(self)} + # noinspection PyProtectedMember + data["version_info"] = data["version_info"]._asdict() # namedtuple to dictionary + return data + + @classmethod + def from_exe(cls, exe, app_data=None, raise_on_error=True, ignore_cache=False, resolve_to_host=True): + """Given a path to an executable get the python information""" + # this method is not used by itself, so here and called functions can import stuff locally + from virtualenv.discovery.cached_py_info import from_exe + + proposed = from_exe(cls, app_data, exe, raise_on_error=raise_on_error, ignore_cache=ignore_cache) + # noinspection PyProtectedMember + if isinstance(proposed, PythonInfo) and resolve_to_host: + try: + proposed = proposed._resolve_to_system(app_data, proposed) + except Exception as exception: + if raise_on_error: + raise exception + logging.info("ignore %s due cannot resolve system due to %r", proposed.original_executable, exception) + proposed = None + return proposed + + @classmethod + def _from_json(cls, payload): + # the dictionary unroll here is to protect against pypy bug of interpreter crashing + raw = json.loads(payload) + return cls._from_dict({k: v for k, v in raw.items()}) + + @classmethod + def _from_dict(cls, data): + data["version_info"] = VersionInfo(**data["version_info"]) # restore this to a named tuple structure + result = cls() + result.__dict__ = {k: v for k, v in data.items()} + return result + + @classmethod + def _resolve_to_system(cls, app_data, target): + start_executable = target.executable + prefixes = OrderedDict() + while target.system_executable is None: + prefix = target.real_prefix or target.base_prefix or target.prefix + if prefix in prefixes: + if len(prefixes) == 1: + # if we're linking back to ourselves accept ourselves with a WARNING + logging.info("%r links back to itself via prefixes", target) + target.system_executable = target.executable + break + for at, (p, t) in enumerate(prefixes.items(), start=1): + logging.error("%d: prefix=%s, info=%r", at, p, t) + logging.error("%d: prefix=%s, info=%r", len(prefixes) + 1, prefix, target) + raise RuntimeError("prefixes are causing a circle {}".format("|".join(prefixes.keys()))) + prefixes[prefix] = target + target = target.discover_exe(app_data, prefix=prefix, exact=False) + if target.executable != target.system_executable: + target = cls.from_exe(target.system_executable, app_data) + target.executable = start_executable + return target + + _cache_exe_discovery = {} + + def discover_exe(self, app_data, prefix, exact=True): + key = prefix, exact + if key in self._cache_exe_discovery and prefix: + logging.debug("discover exe from cache %s - exact %s: %r", prefix, exact, self._cache_exe_discovery[key]) + return self._cache_exe_discovery[key] + logging.debug("discover exe for %s in %s", self, prefix) + # we don't know explicitly here, do some guess work - our executable name should tell + possible_names = self._find_possible_exe_names() + possible_folders = self._find_possible_folders(prefix) + discovered = [] + for folder in possible_folders: + for name in possible_names: + info = self._check_exe(app_data, folder, name, exact, discovered) + if info is not None: + self._cache_exe_discovery[key] = info + return info + if exact is False and discovered: + info = self._select_most_likely(discovered, self) + folders = os.pathsep.join(possible_folders) + self._cache_exe_discovery[key] = info + logging.debug("no exact match found, chosen most similar of %s within base folders %s", info, folders) + return info + msg = "failed to detect {} in {}".format("|".join(possible_names), os.pathsep.join(possible_folders)) + raise RuntimeError(msg) + + def _check_exe(self, app_data, folder, name, exact, discovered): + exe_path = os.path.join(folder, name) + if not os.path.exists(exe_path): + return None + info = self.from_exe(exe_path, app_data, resolve_to_host=False, raise_on_error=False) + if info is None: # ignore if for some reason we can't query + return None + for item in ["implementation", "architecture", "version_info"]: + found = getattr(info, item) + searched = getattr(self, item) + if found != searched: + if item == "version_info": + found, searched = ".".join(str(i) for i in found), ".".join(str(i) for i in searched) + executable = info.executable + logging.debug("refused interpreter %s because %s differs %s != %s", executable, item, found, searched) + if exact is False: + discovered.append(info) + break + else: + return info + return None + + @staticmethod + def _select_most_likely(discovered, target): + # no exact match found, start relaxing our requirements then to facilitate system package upgrades that + # could cause this (when using copy strategy of the host python) + def sort_by(info): + # we need to setup some priority of traits, this is as follows: + # implementation, major, minor, micro, architecture, tag, serial + matches = [ + info.implementation == target.implementation, + info.version_info.major == target.version_info.major, + info.version_info.minor == target.version_info.minor, + info.architecture == target.architecture, + info.version_info.micro == target.version_info.micro, + info.version_info.releaselevel == target.version_info.releaselevel, + info.version_info.serial == target.version_info.serial, + ] + priority = sum((1 << pos if match else 0) for pos, match in enumerate(reversed(matches))) + return priority + + sorted_discovered = sorted(discovered, key=sort_by, reverse=True) # sort by priority in decreasing order + most_likely = sorted_discovered[0] + return most_likely + + def _find_possible_folders(self, inside_folder): + candidate_folder = OrderedDict() + executables = OrderedDict() + executables[os.path.realpath(self.executable)] = None + executables[self.executable] = None + executables[os.path.realpath(self.original_executable)] = None + executables[self.original_executable] = None + for exe in executables.keys(): + base = os.path.dirname(exe) + # following path pattern of the current + if base.startswith(self.prefix): + relative = base[len(self.prefix) :] + candidate_folder["{}{}".format(inside_folder, relative)] = None + + # or at root level + candidate_folder[inside_folder] = None + return list(i for i in candidate_folder.keys() if os.path.exists(i)) + + def _find_possible_exe_names(self): + name_candidate = OrderedDict() + for name in self._possible_base(): + for at in (3, 2, 1, 0): + version = ".".join(str(i) for i in self.version_info[:at]) + for arch in ["-{}".format(self.architecture), ""]: + for ext in EXTENSIONS: + candidate = "{}{}{}{}".format(name, version, arch, ext) + name_candidate[candidate] = None + return list(name_candidate.keys()) + + def _possible_base(self): + possible_base = OrderedDict() + basename = os.path.splitext(os.path.basename(self.executable))[0].rstrip(digits) + possible_base[basename] = None + possible_base[self.implementation] = None + # python is always the final option as in practice is used by multiple implementation as exe name + if "python" in possible_base: + del possible_base["python"] + possible_base["python"] = None + for base in possible_base: + lower = base.lower() + yield lower + from virtualenv.info import fs_is_case_sensitive + + if fs_is_case_sensitive(): + if base != lower: + yield base + upper = base.upper() + if upper != base: + yield upper + + +if __name__ == "__main__": + # dump a JSON representation of the current python + # noinspection PyProtectedMember + print(PythonInfo()._to_json()) diff --git a/robot/lib/python3.8/site-packages/virtualenv/discovery/py_spec.py b/robot/lib/python3.8/site-packages/virtualenv/discovery/py_spec.py new file mode 100644 index 0000000000000000000000000000000000000000..cb63e1516b5b2e97f2d96e1eb7e869935a983343 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/discovery/py_spec.py @@ -0,0 +1,122 @@ +"""A Python specification is an abstract requirement definition of a interpreter""" +from __future__ import absolute_import, unicode_literals + +import os +import re +import sys +from collections import OrderedDict + +from virtualenv.info import fs_is_case_sensitive +from virtualenv.util.six import ensure_str + +PATTERN = re.compile(r"^(?P[a-zA-Z]+)?(?P[0-9.]+)?(?:-(?P32|64))?$") +IS_WIN = sys.platform == "win32" + + +class PythonSpec(object): + """Contains specification about a Python Interpreter""" + + def __init__(self, str_spec, implementation, major, minor, micro, architecture, path): + self.str_spec = str_spec + self.implementation = implementation + self.major = major + self.minor = minor + self.micro = micro + self.architecture = architecture + self.path = path + + @classmethod + def from_string_spec(cls, string_spec): + impl, major, minor, micro, arch, path = None, None, None, None, None, None + if os.path.isabs(string_spec): + path = string_spec + else: + ok = False + match = re.match(PATTERN, string_spec) + if match: + + def _int_or_none(val): + return None if val is None else int(val) + + try: + groups = match.groupdict() + version = groups["version"] + if version is not None: + versions = tuple(int(i) for i in version.split(".") if i) + if len(versions) > 3: + raise ValueError + if len(versions) == 3: + major, minor, micro = versions + elif len(versions) == 2: + major, minor = versions + elif len(versions) == 1: + version_data = versions[0] + major = int(str(version_data)[0]) # first digit major + if version_data > 9: + minor = int(str(version_data)[1:]) + ok = True + except ValueError: + pass + else: + impl = groups["impl"] + if impl == "py" or impl == "python": + impl = "CPython" + arch = _int_or_none(groups["arch"]) + + if not ok: + path = string_spec + + return cls(string_spec, impl, major, minor, micro, arch, path) + + def generate_names(self): + impls = OrderedDict() + if self.implementation: + # first consider implementation as it is + impls[self.implementation] = False + if fs_is_case_sensitive(): + # for case sensitive file systems consider lower and upper case versions too + # trivia: MacBooks and all pre 2018 Windows-es were case insensitive by default + impls[self.implementation.lower()] = False + impls[self.implementation.upper()] = False + impls["python"] = True # finally consider python as alias, implementation must match now + version = self.major, self.minor, self.micro + try: + version = version[: version.index(None)] + except ValueError: + pass + for impl, match in impls.items(): + for at in range(len(version), -1, -1): + cur_ver = version[0:at] + spec = "{}{}".format(impl, ".".join(str(i) for i in cur_ver)) + yield spec, match + + @property + def is_abs(self): + return self.path is not None and os.path.isabs(self.path) + + def satisfies(self, spec): + """called when there's a candidate metadata spec to see if compatible - e.g. PEP-514 on Windows""" + if spec.is_abs and self.is_abs and self.path != spec.path: + return False + if spec.implementation is not None and spec.implementation.lower() != self.implementation.lower(): + return False + if spec.architecture is not None and spec.architecture != self.architecture: + return False + + for our, req in zip((self.major, self.minor, self.micro), (spec.major, spec.minor, spec.micro)): + if req is not None and our is not None and our != req: + return False + return True + + def __unicode__(self): + return "{}({})".format( + type(self).__name__, + ", ".join( + "{}={}".format(k, getattr(self, k)) + for k in ("implementation", "major", "minor", "micro", "architecture", "path") + if getattr(self, k) is not None + ), + ) + + def __repr__(self): + return ensure_str(self.__unicode__()) diff --git a/robot/lib/python3.8/site-packages/virtualenv/discovery/windows/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/discovery/windows/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..556ecf2e4b0c6fc811e75f216e2aae1c5e1bfb16 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/discovery/windows/__init__.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import, unicode_literals + +from ..py_info import PythonInfo +from ..py_spec import PythonSpec +from .pep514 import discover_pythons + + +class Pep514PythonInfo(PythonInfo): + """""" + + +def propose_interpreters(spec, cache_dir): + # see if PEP-514 entries are good + + # start with higher python versions in an effort to use the latest version available + # and prefer PythonCore over conda pythons (as virtualenv is mostly used by non conda tools) + existing = list(discover_pythons()) + existing.sort( + key=lambda i: tuple(-1 if j is None else j for j in i[1:4]) + (1 if i[0] == "PythonCore" else 0,), reverse=True + ) + + for name, major, minor, arch, exe, _ in existing: + # pre-filter + if name in ("PythonCore", "ContinuumAnalytics"): + name = "CPython" + registry_spec = PythonSpec(None, name, major, minor, None, arch, exe) + if registry_spec.satisfies(spec): + interpreter = Pep514PythonInfo.from_exe(exe, cache_dir, raise_on_error=False) + if interpreter is not None: + if interpreter.satisfies(spec, impl_must_match=True): + yield interpreter diff --git a/robot/lib/python3.8/site-packages/virtualenv/discovery/windows/pep514.py b/robot/lib/python3.8/site-packages/virtualenv/discovery/windows/pep514.py new file mode 100644 index 0000000000000000000000000000000000000000..048436a6009fcc41332928804e6e0ae5bae28cd2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/discovery/windows/pep514.py @@ -0,0 +1,161 @@ +"""Implement https://www.python.org/dev/peps/pep-0514/ to discover interpreters - Windows only""" +from __future__ import absolute_import, print_function, unicode_literals + +import os +import re +from logging import basicConfig, getLogger + +import six + +if six.PY3: + import winreg +else: + # noinspection PyUnresolvedReferences + import _winreg as winreg + +LOGGER = getLogger(__name__) + + +def enum_keys(key): + at = 0 + while True: + try: + yield winreg.EnumKey(key, at) + except OSError: + break + at += 1 + + +def get_value(key, value_name): + try: + return winreg.QueryValueEx(key, value_name)[0] + except OSError: + return None + + +def discover_pythons(): + for hive, hive_name, key, flags, default_arch in [ + (winreg.HKEY_CURRENT_USER, "HKEY_CURRENT_USER", r"Software\Python", 0, 64), + (winreg.HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", r"Software\Python", winreg.KEY_WOW64_64KEY, 64), + (winreg.HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", r"Software\Python", winreg.KEY_WOW64_32KEY, 32), + ]: + for spec in process_set(hive, hive_name, key, flags, default_arch): + yield spec + + +def process_set(hive, hive_name, key, flags, default_arch): + try: + with winreg.OpenKeyEx(hive, key, 0, winreg.KEY_READ | flags) as root_key: + for company in enum_keys(root_key): + if company == "PyLauncher": # reserved + continue + for spec in process_company(hive_name, company, root_key, default_arch): + yield spec + except OSError: + pass + + +def process_company(hive_name, company, root_key, default_arch): + with winreg.OpenKeyEx(root_key, company) as company_key: + for tag in enum_keys(company_key): + spec = process_tag(hive_name, company, company_key, tag, default_arch) + if spec is not None: + yield spec + + +def process_tag(hive_name, company, company_key, tag, default_arch): + with winreg.OpenKeyEx(company_key, tag) as tag_key: + version = load_version_data(hive_name, company, tag, tag_key) + if version is not None: # if failed to get version bail + major, minor, _ = version + arch = load_arch_data(hive_name, company, tag, tag_key, default_arch) + if arch is not None: + exe_data = load_exe(hive_name, company, company_key, tag) + if exe_data is not None: + exe, args = exe_data + return company, major, minor, arch, exe, args + + +def load_exe(hive_name, company, company_key, tag): + key_path = "{}/{}/{}".format(hive_name, company, tag) + try: + with winreg.OpenKeyEx(company_key, r"{}\InstallPath".format(tag)) as ip_key: + with ip_key: + exe = get_value(ip_key, "ExecutablePath") + if exe is None: + ip = get_value(ip_key, None) + if ip is None: + msg(key_path, "no ExecutablePath or default for it") + + else: + exe = os.path.join(ip, str("python.exe")) + if exe is not None and os.path.exists(exe): + args = get_value(ip_key, "ExecutableArguments") + return exe, args + else: + msg(key_path, "could not load exe with value {}".format(exe)) + except OSError: + msg("{}/{}".format(key_path, "InstallPath"), "missing") + return None + + +def load_arch_data(hive_name, company, tag, tag_key, default_arch): + arch_str = get_value(tag_key, "SysArchitecture") + if arch_str is not None: + key_path = "{}/{}/{}/SysArchitecture".format(hive_name, company, tag) + try: + return parse_arch(arch_str) + except ValueError as sys_arch: + msg(key_path, sys_arch) + return default_arch + + +def parse_arch(arch_str): + if isinstance(arch_str, six.string_types): + match = re.match(r"^(\d+)bit$", arch_str) + if match: + return int(next(iter(match.groups()))) + error = "invalid format {}".format(arch_str) + else: + error = "arch is not string: {}".format(repr(arch_str)) + raise ValueError(error) + + +def load_version_data(hive_name, company, tag, tag_key): + for candidate, key_path in [ + (get_value(tag_key, "SysVersion"), "{}/{}/{}/SysVersion".format(hive_name, company, tag)), + (tag, "{}/{}/{}".format(hive_name, company, tag)), + ]: + if candidate is not None: + try: + return parse_version(candidate) + except ValueError as sys_version: + msg(key_path, sys_version) + return None + + +def parse_version(version_str): + if isinstance(version_str, six.string_types): + match = re.match(r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?$", version_str) + if match: + return tuple(int(i) if i is not None else None for i in match.groups()) + error = "invalid format {}".format(version_str) + else: + error = "version is not string: {}".format(repr(version_str)) + raise ValueError(error) + + +def msg(path, what): + LOGGER.warning("PEP-514 violation in Windows Registry at {} error: {}".format(path, what)) + + +def _run(): + basicConfig() + interpreters = [] + for spec in discover_pythons(): + interpreters.append(repr(spec)) + print("\n".join(sorted(interpreters))) + + +if __name__ == "__main__": + _run() diff --git a/robot/lib/python3.8/site-packages/virtualenv/info.py b/robot/lib/python3.8/site-packages/virtualenv/info.py new file mode 100644 index 0000000000000000000000000000000000000000..afe4097736dd0ca2ca5d802ecd694324d74b22ec --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/info.py @@ -0,0 +1,65 @@ +from __future__ import absolute_import, unicode_literals + +import logging +import os +import platform +import sys +import tempfile + +IMPLEMENTATION = platform.python_implementation() +IS_PYPY = IMPLEMENTATION == "PyPy" +IS_CPYTHON = IMPLEMENTATION == "CPython" +PY3 = sys.version_info[0] == 3 +PY2 = sys.version_info[0] == 2 +IS_WIN = sys.platform == "win32" +ROOT = os.path.realpath(os.path.join(os.path.abspath(__file__), os.path.pardir, os.path.pardir)) +IS_ZIPAPP = os.path.isfile(ROOT) +WIN_CPYTHON_2 = IS_CPYTHON and IS_WIN and PY2 + +_CAN_SYMLINK = _FS_CASE_SENSITIVE = _CFG_DIR = _DATA_DIR = None + + +def fs_is_case_sensitive(): + global _FS_CASE_SENSITIVE + + if _FS_CASE_SENSITIVE is None: + with tempfile.NamedTemporaryFile(prefix="TmP") as tmp_file: + _FS_CASE_SENSITIVE = not os.path.exists(tmp_file.name.lower()) + logging.debug("filesystem is %scase-sensitive", "" if _FS_CASE_SENSITIVE else "not ") + return _FS_CASE_SENSITIVE + + +def fs_supports_symlink(): + global _CAN_SYMLINK + + if _CAN_SYMLINK is None: + can = False + if hasattr(os, "symlink"): + if IS_WIN: + with tempfile.NamedTemporaryFile(prefix="TmP") as tmp_file: + temp_dir = os.path.dirname(tmp_file.name) + dest = os.path.join(temp_dir, "{}-{}".format(tmp_file.name, "b")) + try: + os.symlink(tmp_file.name, dest) + can = True + except (OSError, NotImplementedError): + pass + logging.debug("symlink on filesystem does%s work", "" if can else " not") + else: + can = True + _CAN_SYMLINK = can + return _CAN_SYMLINK + + +__all__ = ( + "IS_PYPY", + "IS_CPYTHON", + "PY3", + "PY2", + "IS_WIN", + "fs_is_case_sensitive", + "fs_supports_symlink", + "ROOT", + "IS_ZIPAPP", + "WIN_CPYTHON_2", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/report.py b/robot/lib/python3.8/site-packages/virtualenv/report.py new file mode 100644 index 0000000000000000000000000000000000000000..665b293cb20231239cf2786ca1cd485ee94af2de --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/report.py @@ -0,0 +1,57 @@ +from __future__ import absolute_import, unicode_literals + +import logging +import sys + +from virtualenv.util.six import ensure_str + +LEVELS = { + 0: logging.CRITICAL, + 1: logging.ERROR, + 2: logging.WARNING, + 3: logging.INFO, + 4: logging.DEBUG, + 5: logging.NOTSET, +} + +MAX_LEVEL = max(LEVELS.keys()) +LOGGER = logging.getLogger() + + +def setup_report(verbosity, show_pid=False): + _clean_handlers(LOGGER) + if verbosity > MAX_LEVEL: + verbosity = MAX_LEVEL # pragma: no cover + level = LEVELS[verbosity] + msg_format = "%(message)s" + filelock_logger = logging.getLogger("filelock") + if level <= logging.DEBUG: + locate = "module" + msg_format = "%(relativeCreated)d {} [%(levelname)s %({})s:%(lineno)d]".format(msg_format, locate) + filelock_logger.setLevel(level) + else: + filelock_logger.setLevel(logging.WARN) + if show_pid: + msg_format = "[%(process)d] " + msg_format + formatter = logging.Formatter(ensure_str(msg_format)) + stream_handler = logging.StreamHandler(stream=sys.stdout) + stream_handler.setLevel(level) + LOGGER.setLevel(logging.NOTSET) + stream_handler.setFormatter(formatter) + LOGGER.addHandler(stream_handler) + level_name = logging.getLevelName(level) + logging.debug("setup logging to %s", level_name) + logging.getLogger("distlib").setLevel(logging.ERROR) + return verbosity + + +def _clean_handlers(log): + for log_handler in list(log.handlers): # remove handlers of libraries + log.removeHandler(log_handler) + + +__all__ = ( + "LEVELS", + "MAX_LEVEL", + "setup_report", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/run/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/run/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..66083df82b7d42b55e2061ca81c7253690392054 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/run/__init__.py @@ -0,0 +1,151 @@ +from __future__ import absolute_import, unicode_literals + +import logging +from functools import partial + +from ..app_data import make_app_data +from ..config.cli.parser import VirtualEnvConfigParser +from ..report import LEVELS, setup_report +from ..run.session import Session +from ..seed.wheels.periodic_update import manual_upgrade +from ..version import __version__ +from .plugin.activators import ActivationSelector +from .plugin.creators import CreatorSelector +from .plugin.discovery import get_discover +from .plugin.seeders import SeederSelector + + +def cli_run(args, options=None, setup_logging=True): + """ + Create a virtual environment given some command line interface arguments. + + :param args: the command line arguments + :param options: passing in a ``VirtualEnvOptions`` object allows return of the parsed options + :param setup_logging: ``True`` if setup logging handlers, ``False`` to use handlers already registered + :return: the session object of the creation (its structure for now is experimental and might change on short notice) + """ + of_session = session_via_cli(args, options, setup_logging) + with of_session: + of_session.run() + return of_session + + +def session_via_cli(args, options=None, setup_logging=True): + """ + Create a virtualenv session (same as cli_run, but this does not perform the creation). Use this if you just want to + query what the virtual environment would look like, but not actually create it. + + :param args: the command line arguments + :param options: passing in a ``VirtualEnvOptions`` object allows return of the parsed options + :param setup_logging: ``True`` if setup logging handlers, ``False`` to use handlers already registered + :return: the session object of the creation (its structure for now is experimental and might change on short notice) + """ + parser, elements = build_parser(args, options, setup_logging) + options = parser.parse_args(args) + creator, seeder, activators = tuple(e.create(options) for e in elements) # create types + of_session = Session(options.verbosity, options.app_data, parser._interpreter, creator, seeder, activators) # noqa + return of_session + + +def build_parser(args=None, options=None, setup_logging=True): + parser = VirtualEnvConfigParser(options) + add_version_flag(parser) + parser.add_argument( + "--with-traceback", + dest="with_traceback", + action="store_true", + default=False, + help="on failure also display the stacktrace internals of virtualenv", + ) + _do_report_setup(parser, args, setup_logging) + options = load_app_data(args, parser, options) + handle_extra_commands(options) + + discover = get_discover(parser, args) + parser._interpreter = interpreter = discover.interpreter + if interpreter is None: + raise RuntimeError("failed to find interpreter for {}".format(discover)) + elements = [ + CreatorSelector(interpreter, parser), + SeederSelector(interpreter, parser), + ActivationSelector(interpreter, parser), + ] + options, _ = parser.parse_known_args(args) + for element in elements: + element.handle_selected_arg_parse(options) + parser.enable_help() + return parser, elements + + +def build_parser_only(args=None): + """Used to provide a parser for the doc generation""" + return build_parser(args)[0] + + +def handle_extra_commands(options): + if options.upgrade_embed_wheels: + result = manual_upgrade(options.app_data) + raise SystemExit(result) + + +def load_app_data(args, parser, options): + parser.add_argument( + "--read-only-app-data", + action="store_true", + help="use app data folder in read-only mode (write operations will fail with error)", + ) + options, _ = parser.parse_known_args(args, namespace=options) + + # here we need a write-able application data (e.g. the zipapp might need this for discovery cache) + parser.add_argument( + "--app-data", + help="a data folder used as cache by the virtualenv", + type=partial(make_app_data, read_only=options.read_only_app_data), + default=make_app_data(None, read_only=options.read_only_app_data), + ) + parser.add_argument( + "--reset-app-data", + action="store_true", + help="start with empty app data folder", + ) + parser.add_argument( + "--upgrade-embed-wheels", + action="store_true", + help="trigger a manual update of the embedded wheels", + ) + options, _ = parser.parse_known_args(args, namespace=options) + if options.reset_app_data: + options.app_data.reset() + return options + + +def add_version_flag(parser): + import virtualenv + + parser.add_argument( + "--version", + action="version", + version="%(prog)s {} from {}".format(__version__, virtualenv.__file__), + help="display the version of the virtualenv package and its location, then exit", + ) + + +def _do_report_setup(parser, args, setup_logging): + level_map = ", ".join("{}={}".format(logging.getLevelName(l), c) for c, l in sorted(list(LEVELS.items()))) + msg = "verbosity = verbose - quiet, default {}, mapping => {}" + verbosity_group = parser.add_argument_group( + title="verbosity", + description=msg.format(logging.getLevelName(LEVELS[3]), level_map), + ) + verbosity = verbosity_group.add_mutually_exclusive_group() + verbosity.add_argument("-v", "--verbose", action="count", dest="verbose", help="increase verbosity", default=2) + verbosity.add_argument("-q", "--quiet", action="count", dest="quiet", help="decrease verbosity", default=0) + option, _ = parser.parse_known_args(args) + if setup_logging: + setup_report(option.verbosity) + + +__all__ = ( + "cli_run", + "session_via_cli", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/run/plugin/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/run/plugin/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/virtualenv/run/plugin/activators.py b/robot/lib/python3.8/site-packages/virtualenv/run/plugin/activators.py new file mode 100644 index 0000000000000000000000000000000000000000..dea28277f1218f23f3a4fdadb439cda7a4296f0a --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/run/plugin/activators.py @@ -0,0 +1,53 @@ +from __future__ import absolute_import, unicode_literals + +from argparse import ArgumentTypeError +from collections import OrderedDict + +from .base import ComponentBuilder + + +class ActivationSelector(ComponentBuilder): + def __init__(self, interpreter, parser): + self.default = None + possible = OrderedDict( + (k, v) for k, v in self.options("virtualenv.activate").items() if v.supports(interpreter) + ) + super(ActivationSelector, self).__init__(interpreter, parser, "activators", possible) + self.parser.description = "options for activation scripts" + self.active = None + + def add_selector_arg_parse(self, name, choices): + self.default = ",".join(choices) + self.parser.add_argument( + "--{}".format(name), + default=self.default, + metavar="comma_sep_list", + required=False, + help="activators to generate - default is all supported", + type=self._extract_activators, + ) + + def _extract_activators(self, entered_str): + elements = [e.strip() for e in entered_str.split(",") if e.strip()] + missing = [e for e in elements if e not in self.possible] + if missing: + raise ArgumentTypeError("the following activators are not available {}".format(",".join(missing))) + return elements + + def handle_selected_arg_parse(self, options): + selected_activators = ( + self._extract_activators(self.default) if options.activators is self.default else options.activators + ) + self.active = {k: v for k, v in self.possible.items() if k in selected_activators} + self.parser.add_argument( + "--prompt", + dest="prompt", + metavar="prompt", + help="provides an alternative prompt prefix for this environment", + default=None, + ) + for activator in self.active.values(): + activator.add_parser_arguments(self.parser, self.interpreter) + + def create(self, options): + return [activator_class(options) for activator_class in self.active.values()] diff --git a/robot/lib/python3.8/site-packages/virtualenv/run/plugin/base.py b/robot/lib/python3.8/site-packages/virtualenv/run/plugin/base.py new file mode 100644 index 0000000000000000000000000000000000000000..ed10fe0e2731de09aca1d2ef1f40be1409122a72 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/run/plugin/base.py @@ -0,0 +1,58 @@ +from __future__ import absolute_import, unicode_literals + +import sys +from collections import OrderedDict + +if sys.version_info >= (3, 8): + from importlib.metadata import entry_points +else: + from importlib_metadata import entry_points + + +class PluginLoader(object): + _OPTIONS = None + _ENTRY_POINTS = None + + @classmethod + def entry_points_for(cls, key): + return OrderedDict((e.name, e.load()) for e in cls.entry_points().get(key, {})) + + @staticmethod + def entry_points(): + if PluginLoader._ENTRY_POINTS is None: + PluginLoader._ENTRY_POINTS = entry_points() + return PluginLoader._ENTRY_POINTS + + +class ComponentBuilder(PluginLoader): + def __init__(self, interpreter, parser, name, possible): + self.interpreter = interpreter + self.name = name + self._impl_class = None + self.possible = possible + self.parser = parser.add_argument_group(title=name) + self.add_selector_arg_parse(name, list(self.possible)) + + @classmethod + def options(cls, key): + if cls._OPTIONS is None: + cls._OPTIONS = cls.entry_points_for(key) + return cls._OPTIONS + + def add_selector_arg_parse(self, name, choices): + raise NotImplementedError + + def handle_selected_arg_parse(self, options): + selected = getattr(options, self.name) + if selected not in self.possible: + raise RuntimeError("No implementation for {}".format(self.interpreter)) + self._impl_class = self.possible[selected] + self.populate_selected_argparse(selected, options.app_data) + return selected + + def populate_selected_argparse(self, selected, app_data): + self.parser.description = "options for {} {}".format(self.name, selected) + self._impl_class.add_parser_arguments(self.parser, self.interpreter, app_data) + + def create(self, options): + return self._impl_class(options, self.interpreter) diff --git a/robot/lib/python3.8/site-packages/virtualenv/run/plugin/creators.py b/robot/lib/python3.8/site-packages/virtualenv/run/plugin/creators.py new file mode 100644 index 0000000000000000000000000000000000000000..ef4177a5951e487a8bf5904ea54b84dacfe2a617 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/run/plugin/creators.py @@ -0,0 +1,77 @@ +from __future__ import absolute_import, unicode_literals + +from collections import OrderedDict, defaultdict, namedtuple + +from virtualenv.create.describe import Describe +from virtualenv.create.via_global_ref.builtin.builtin_way import VirtualenvBuiltin + +from .base import ComponentBuilder + +CreatorInfo = namedtuple("CreatorInfo", ["key_to_class", "key_to_meta", "describe", "builtin_key"]) + + +class CreatorSelector(ComponentBuilder): + def __init__(self, interpreter, parser): + creators, self.key_to_meta, self.describe, self.builtin_key = self.for_interpreter(interpreter) + super(CreatorSelector, self).__init__(interpreter, parser, "creator", creators) + + @classmethod + def for_interpreter(cls, interpreter): + key_to_class, key_to_meta, builtin_key, describe = OrderedDict(), {}, None, None + errors = defaultdict(list) + for key, creator_class in cls.options("virtualenv.create").items(): + if key == "builtin": + raise RuntimeError("builtin creator is a reserved name") + meta = creator_class.can_create(interpreter) + if meta: + if meta.error: + errors[meta.error].append(creator_class) + else: + if "builtin" not in key_to_class and issubclass(creator_class, VirtualenvBuiltin): + builtin_key = key + key_to_class["builtin"] = creator_class + key_to_meta["builtin"] = meta + key_to_class[key] = creator_class + key_to_meta[key] = meta + if describe is None and issubclass(creator_class, Describe) and creator_class.can_describe(interpreter): + describe = creator_class + if not key_to_meta: + if errors: + rows = ["{} for creators {}".format(k, ", ".join(i.__name__ for i in v)) for k, v in errors.items()] + raise RuntimeError("\n".join(rows)) + else: + raise RuntimeError("No virtualenv implementation for {}".format(interpreter)) + return CreatorInfo( + key_to_class=key_to_class, + key_to_meta=key_to_meta, + describe=describe, + builtin_key=builtin_key, + ) + + def add_selector_arg_parse(self, name, choices): + # prefer the built-in venv if present, otherwise fallback to first defined type + choices = sorted(choices, key=lambda a: 0 if a == "builtin" else 1) + default_value = self._get_default(choices) + self.parser.add_argument( + "--{}".format(name), + choices=choices, + default=default_value, + required=False, + help="create environment via{}".format( + "" if self.builtin_key is None else " (builtin = {})".format(self.builtin_key), + ), + ) + + @staticmethod + def _get_default(choices): + return next(iter(choices)) + + def populate_selected_argparse(self, selected, app_data): + self.parser.description = "options for {} {}".format(self.name, selected) + self._impl_class.add_parser_arguments(self.parser, self.interpreter, self.key_to_meta[selected], app_data) + + def create(self, options): + options.meta = self.key_to_meta[getattr(options, self.name)] + if not issubclass(self._impl_class, Describe): + options.describe = self.describe(options, self.interpreter) + return super(CreatorSelector, self).create(options) diff --git a/robot/lib/python3.8/site-packages/virtualenv/run/plugin/discovery.py b/robot/lib/python3.8/site-packages/virtualenv/run/plugin/discovery.py new file mode 100644 index 0000000000000000000000000000000000000000..3b6fc60d8392fdffc47ea04334bfc66aa4567abb --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/run/plugin/discovery.py @@ -0,0 +1,32 @@ +from __future__ import absolute_import, unicode_literals + +from .base import PluginLoader + + +class Discovery(PluginLoader): + """""" + + +def get_discover(parser, args): + discover_types = Discovery.entry_points_for("virtualenv.discovery") + discovery_parser = parser.add_argument_group( + title="discovery", + description="discover and provide a target interpreter", + ) + discovery_parser.add_argument( + "--discovery", + choices=_get_default_discovery(discover_types), + default=next(i for i in discover_types.keys()), + required=False, + help="interpreter discovery method", + ) + options, _ = parser.parse_known_args(args) + discover_class = discover_types[options.discovery] + discover_class.add_parser_arguments(discovery_parser) + options, _ = parser.parse_known_args(args, namespace=options) + discover = discover_class(options) + return discover + + +def _get_default_discovery(discover_types): + return list(discover_types.keys()) diff --git a/robot/lib/python3.8/site-packages/virtualenv/run/plugin/seeders.py b/robot/lib/python3.8/site-packages/virtualenv/run/plugin/seeders.py new file mode 100644 index 0000000000000000000000000000000000000000..d182c6f731742f871dd259ac0eaa9b7cac794a42 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/run/plugin/seeders.py @@ -0,0 +1,35 @@ +from __future__ import absolute_import, unicode_literals + +from .base import ComponentBuilder + + +class SeederSelector(ComponentBuilder): + def __init__(self, interpreter, parser): + possible = self.options("virtualenv.seed") + super(SeederSelector, self).__init__(interpreter, parser, "seeder", possible) + + def add_selector_arg_parse(self, name, choices): + self.parser.add_argument( + "--{}".format(name), + choices=choices, + default=self._get_default(), + required=False, + help="seed packages install method", + ) + self.parser.add_argument( + "--no-seed", + "--without-pip", + help="do not install seed packages", + action="store_true", + dest="no_seed", + ) + + @staticmethod + def _get_default(): + return "app-data" + + def handle_selected_arg_parse(self, options): + return super(SeederSelector, self).handle_selected_arg_parse(options) + + def create(self, options): + return self._impl_class(options) diff --git a/robot/lib/python3.8/site-packages/virtualenv/run/session.py b/robot/lib/python3.8/site-packages/virtualenv/run/session.py new file mode 100644 index 0000000000000000000000000000000000000000..24836d2855da8b6459cb22aa62dccde87f1f16f1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/run/session.py @@ -0,0 +1,91 @@ +from __future__ import absolute_import, unicode_literals + +import json +import logging + +from virtualenv.util.six import ensure_text + + +class Session(object): + """Represents a virtual environment creation session""" + + def __init__(self, verbosity, app_data, interpreter, creator, seeder, activators): + self._verbosity = verbosity + self._app_data = app_data + self._interpreter = interpreter + self._creator = creator + self._seeder = seeder + self._activators = activators + + @property + def verbosity(self): + """The verbosity of the run""" + return self._verbosity + + @property + def interpreter(self): + """Create a virtual environment based on this reference interpreter""" + return self._interpreter + + @property + def creator(self): + """The creator used to build the virtual environment (must be compatible with the interpreter)""" + return self._creator + + @property + def seeder(self): + """The mechanism used to provide the seed packages (pip, setuptools, wheel)""" + return self._seeder + + @property + def activators(self): + """Activators used to generate activations scripts""" + return self._activators + + def run(self): + self._create() + self._seed() + self._activate() + self.creator.pyenv_cfg.write() + + def _create(self): + logging.info("create virtual environment via %s", ensure_text(str(self.creator))) + self.creator.run() + logging.debug(_DEBUG_MARKER) + logging.debug("%s", _Debug(self.creator)) + + def _seed(self): + if self.seeder is not None and self.seeder.enabled: + logging.info("add seed packages via %s", self.seeder) + self.seeder.run(self.creator) + + def _activate(self): + if self.activators: + logging.info( + "add activators for %s", + ", ".join(type(i).__name__.replace("Activator", "") for i in self.activators), + ) + for activator in self.activators: + activator.generate(self.creator) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self._app_data.close() + + +_DEBUG_MARKER = "=" * 30 + " target debug " + "=" * 30 + + +class _Debug(object): + """lazily populate debug""" + + def __init__(self, creator): + self.creator = creator + + def __unicode__(self): + return ensure_text(repr(self)) + + def __repr__(self): + return json.dumps(self.creator.debug, indent=2) diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/seed/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..01e6d4f49d5f5d8103b5120746b16d0cc56add77 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import, unicode_literals diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/embed/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/embed/base_embed.py b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/base_embed.py new file mode 100644 index 0000000000000000000000000000000000000000..c794e834de36f0dd58e15be731e332a10e72d609 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/base_embed.py @@ -0,0 +1,118 @@ +from __future__ import absolute_import, unicode_literals + +from abc import ABCMeta + +from six import add_metaclass + +from virtualenv.util.path import Path +from virtualenv.util.six import ensure_str, ensure_text + +from ..seeder import Seeder +from ..wheels import Version + +PERIODIC_UPDATE_ON_BY_DEFAULT = True + + +@add_metaclass(ABCMeta) +class BaseEmbed(Seeder): + def __init__(self, options): + super(BaseEmbed, self).__init__(options, enabled=options.no_seed is False) + + self.download = options.download + self.extra_search_dir = [i.resolve() for i in options.extra_search_dir if i.exists()] + + self.pip_version = options.pip + self.setuptools_version = options.setuptools + self.wheel_version = options.wheel + + self.no_pip = options.no_pip + self.no_setuptools = options.no_setuptools + self.no_wheel = options.no_wheel + self.app_data = options.app_data + self.periodic_update = not options.no_periodic_update + + if not self.distribution_to_versions(): + self.enabled = False + + @classmethod + def distributions(cls): + return { + "pip": Version.bundle, + "setuptools": Version.bundle, + "wheel": Version.bundle, + } + + def distribution_to_versions(self): + return { + distribution: getattr(self, "{}_version".format(distribution)) + for distribution in self.distributions() + if getattr(self, "no_{}".format(distribution)) is False + } + + @classmethod + def add_parser_arguments(cls, parser, interpreter, app_data): + group = parser.add_mutually_exclusive_group() + group.add_argument( + "--no-download", + "--never-download", + dest="download", + action="store_false", + help="pass to disable download of the latest {} from PyPI".format("/".join(cls.distributions())), + default=True, + ) + group.add_argument( + "--download", + dest="download", + action="store_true", + help="pass to enable download of the latest {} from PyPI".format("/".join(cls.distributions())), + default=False, + ) + parser.add_argument( + "--extra-search-dir", + metavar="d", + type=Path, + nargs="+", + help="a path containing wheels to extend the internal wheel list (can be set 1+ times)", + default=[], + ) + for distribution, default in cls.distributions().items(): + parser.add_argument( + "--{}".format(distribution), + dest=distribution, + metavar="version", + help="version of {} to install as seed: embed, bundle or exact version".format(distribution), + default=default, + ) + for distribution in cls.distributions(): + parser.add_argument( + "--no-{}".format(distribution), + dest="no_{}".format(distribution), + action="store_true", + help="do not install {}".format(distribution), + default=False, + ) + parser.add_argument( + "--no-periodic-update", + dest="no_periodic_update", + action="store_true", + help="disable the periodic (once every 14 days) update of the embedded wheels", + default=not PERIODIC_UPDATE_ON_BY_DEFAULT, + ) + + def __unicode__(self): + result = self.__class__.__name__ + result += "(" + if self.extra_search_dir: + result += "extra_search_dir={},".format(", ".join(ensure_text(str(i)) for i in self.extra_search_dir)) + result += "download={},".format(self.download) + for distribution in self.distributions(): + if getattr(self, "no_{}".format(distribution)): + continue + result += " {}{},".format( + distribution, + "={}".format(getattr(self, "{}_version".format(distribution), None) or "latest"), + ) + return result[:-1] + ")" + + def __repr__(self): + return ensure_str(self.__unicode__()) diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/embed/pip_invoke.py b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/pip_invoke.py new file mode 100644 index 0000000000000000000000000000000000000000..372e140dc41fd07934efc10a5a4e65185eb682b2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/pip_invoke.py @@ -0,0 +1,56 @@ +from __future__ import absolute_import, unicode_literals + +import logging +from contextlib import contextmanager + +from virtualenv.discovery.cached_py_info import LogCmd +from virtualenv.seed.embed.base_embed import BaseEmbed +from virtualenv.util.subprocess import Popen + +from ..wheels import Version, get_wheel, pip_wheel_env_run + + +class PipInvoke(BaseEmbed): + def __init__(self, options): + super(PipInvoke, self).__init__(options) + + def run(self, creator): + if not self.enabled: + return + for_py_version = creator.interpreter.version_release_str + with self.get_pip_install_cmd(creator.exe, for_py_version) as cmd: + env = pip_wheel_env_run(self.extra_search_dir, self.app_data) + self._execute(cmd, env) + + @staticmethod + def _execute(cmd, env): + logging.debug("pip seed by running: %s", LogCmd(cmd, env)) + process = Popen(cmd, env=env) + process.communicate() + if process.returncode != 0: + raise RuntimeError("failed seed with code {}".format(process.returncode)) + return process + + @contextmanager + def get_pip_install_cmd(self, exe, for_py_version): + cmd = [str(exe), "-m", "pip", "-q", "install", "--only-binary", ":all:", "--disable-pip-version-check"] + if not self.download: + cmd.append("--no-index") + folders = set() + for dist, version in self.distribution_to_versions().items(): + wheel = get_wheel( + distribution=dist, + version=version, + for_py_version=for_py_version, + search_dirs=self.extra_search_dir, + download=False, + app_data=self.app_data, + do_periodic_update=self.periodic_update, + ) + if wheel is None: + raise RuntimeError("could not get wheel for distribution {}".format(dist)) + folders.add(str(wheel.path.parent)) + cmd.append(Version.as_pip_req(dist, wheel.version)) + for folder in sorted(folders): + cmd.extend(["--find-links", str(folder)]) + yield cmd diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/pip_install/base.py b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/pip_install/base.py new file mode 100644 index 0000000000000000000000000000000000000000..a1d946d50920e47a72dd4eeedaa5a9eae5458b8f --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/pip_install/base.py @@ -0,0 +1,158 @@ +from __future__ import absolute_import, unicode_literals + +import logging +import os +import re +import zipfile +from abc import ABCMeta, abstractmethod +from tempfile import mkdtemp + +from distlib.scripts import ScriptMaker, enquote_executable +from six import PY3, add_metaclass + +from virtualenv.util import ConfigParser +from virtualenv.util.path import Path, safe_delete +from virtualenv.util.six import ensure_text + + +@add_metaclass(ABCMeta) +class PipInstall(object): + def __init__(self, wheel, creator, image_folder): + self._wheel = wheel + self._creator = creator + self._image_dir = image_folder + self._extracted = False + self.__dist_info = None + self._console_entry_points = None + + @abstractmethod + def _sync(self, src, dst): + raise NotImplementedError + + def install(self, version_info): + self._extracted = True + # sync image + for filename in self._image_dir.iterdir(): + into = self._creator.purelib / filename.name + if into.exists(): + if into.is_dir() and not into.is_symlink(): + safe_delete(into) + else: + into.unlink() + self._sync(filename, into) + # generate console executables + consoles = set() + script_dir = self._creator.script_dir + for name, module in self._console_scripts.items(): + consoles.update(self._create_console_entry_point(name, module, script_dir, version_info)) + logging.debug("generated console scripts %s", " ".join(i.name for i in consoles)) + + def build_image(self): + # 1. first extract the wheel + logging.debug("build install image for %s to %s", self._wheel.name, self._image_dir) + with zipfile.ZipFile(str(self._wheel)) as zip_ref: + zip_ref.extractall(str(self._image_dir)) + self._extracted = True + # 2. now add additional files not present in the distribution + new_files = self._generate_new_files() + # 3. finally fix the records file + self._fix_records(new_files) + + def _records_text(self, files): + record_data = "\n".join( + "{},,".format(os.path.relpath(ensure_text(str(rec)), ensure_text(str(self._image_dir)))) for rec in files + ) + return record_data + + def _generate_new_files(self): + new_files = set() + installer = self._dist_info / "INSTALLER" + installer.write_text("pip\n") + new_files.add(installer) + # inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226 + marker = self._image_dir / "{}.virtualenv".format(self._dist_info.stem) + marker.write_text("") + new_files.add(marker) + folder = mkdtemp() + try: + to_folder = Path(folder) + rel = os.path.relpath(ensure_text(str(self._creator.script_dir)), ensure_text(str(self._creator.purelib))) + version_info = self._creator.interpreter.version_info + for name, module in self._console_scripts.items(): + new_files.update( + Path(os.path.normpath(ensure_text(str(self._image_dir / rel / i.name)))) + for i in self._create_console_entry_point(name, module, to_folder, version_info) + ) + finally: + safe_delete(folder) + return new_files + + @property + def _dist_info(self): + if self._extracted is False: + return None # pragma: no cover + if self.__dist_info is None: + files = [] + for filename in self._image_dir.iterdir(): + files.append(filename.name) + if filename.suffix == ".dist-info": + self.__dist_info = filename + break + else: + msg = "no .dist-info at {}, has {}".format(self._image_dir, ", ".join(files)) # pragma: no cover + raise RuntimeError(msg) # pragma: no cover + return self.__dist_info + + @abstractmethod + def _fix_records(self, extra_record_data): + raise NotImplementedError + + @property + def _console_scripts(self): + if self._extracted is False: + return None # pragma: no cover + if self._console_entry_points is None: + self._console_entry_points = {} + entry_points = self._dist_info / "entry_points.txt" + if entry_points.exists(): + parser = ConfigParser.ConfigParser() + with entry_points.open() as file_handler: + reader = getattr(parser, "read_file" if PY3 else "readfp") + reader(file_handler) + if "console_scripts" in parser.sections(): + for name, value in parser.items("console_scripts"): + match = re.match(r"(.*?)-?\d\.?\d*", name) + if match: + name = match.groups(1)[0] + self._console_entry_points[name] = value + return self._console_entry_points + + def _create_console_entry_point(self, name, value, to_folder, version_info): + result = [] + maker = ScriptMakerCustom(to_folder, version_info, self._creator.exe, name) + specification = "{} = {}".format(name, value) + new_files = maker.make(specification) + result.extend(Path(i) for i in new_files) + return result + + def clear(self): + if self._image_dir.exists(): + safe_delete(self._image_dir) + + def has_image(self): + return self._image_dir.exists() and next(self._image_dir.iterdir()) is not None + + +class ScriptMakerCustom(ScriptMaker): + def __init__(self, target_dir, version_info, executable, name): + super(ScriptMakerCustom, self).__init__(None, str(target_dir)) + self.clobber = True # overwrite + self.set_mode = True # ensure they are executable + self.executable = enquote_executable(str(executable)) + self.version_info = version_info.major, version_info.minor + self.variants = {"", "X", "X.Y"} + self._name = name + + def _write_script(self, names, shebang, script_bytes, filenames, ext): + names.add("{}{}.{}".format(self._name, *self.version_info)) + super(ScriptMakerCustom, self)._write_script(names, shebang, script_bytes, filenames, ext) diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/pip_install/copy.py b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/pip_install/copy.py new file mode 100644 index 0000000000000000000000000000000000000000..29d0bc88d11c4365e167de43b4e9f7df6d92dd18 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/pip_install/copy.py @@ -0,0 +1,35 @@ +from __future__ import absolute_import, unicode_literals + +import os + +from virtualenv.util.path import Path, copy +from virtualenv.util.six import ensure_text + +from .base import PipInstall + + +class CopyPipInstall(PipInstall): + def _sync(self, src, dst): + copy(src, dst) + + def _generate_new_files(self): + # create the pyc files + new_files = super(CopyPipInstall, self)._generate_new_files() + new_files.update(self._cache_files()) + return new_files + + def _cache_files(self): + version = self._creator.interpreter.version_info + py_c_ext = ".{}-{}{}.pyc".format(self._creator.interpreter.implementation.lower(), version.major, version.minor) + for root, dirs, files in os.walk(ensure_text(str(self._image_dir)), topdown=True): + root_path = Path(root) + for name in files: + if name.endswith(".py"): + yield root_path / "{}{}".format(name[:-3], py_c_ext) + for name in dirs: + yield root_path / name / "__pycache__" + + def _fix_records(self, new_files): + extra_record_data_str = self._records_text(new_files) + with open(ensure_text(str(self._dist_info / "RECORD")), "ab") as file_handler: + file_handler.write(extra_record_data_str.encode("utf-8")) diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/pip_install/symlink.py b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/pip_install/symlink.py new file mode 100644 index 0000000000000000000000000000000000000000..f958b6545125b9881134efe93b0d42bcce9faedb --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/pip_install/symlink.py @@ -0,0 +1,61 @@ +from __future__ import absolute_import, unicode_literals + +import os +import subprocess +from stat import S_IREAD, S_IRGRP, S_IROTH + +from virtualenv.util.path import safe_delete, set_tree +from virtualenv.util.six import ensure_text +from virtualenv.util.subprocess import Popen + +from .base import PipInstall + + +class SymlinkPipInstall(PipInstall): + def _sync(self, src, dst): + src_str = ensure_text(str(src)) + dest_str = ensure_text(str(dst)) + os.symlink(src_str, dest_str) + + def _generate_new_files(self): + # create the pyc files, as the build image will be R/O + process = Popen( + [ensure_text(str(self._creator.exe)), "-m", "compileall", ensure_text(str(self._image_dir))], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + process.communicate() + # the root pyc is shared, so we'll not symlink that - but still add the pyc files to the RECORD for close + root_py_cache = self._image_dir / "__pycache__" + new_files = set() + if root_py_cache.exists(): + new_files.update(root_py_cache.iterdir()) + new_files.add(root_py_cache) + safe_delete(root_py_cache) + core_new_files = super(SymlinkPipInstall, self)._generate_new_files() + # remove files that are within the image folder deeper than one level (as these will be not linked directly) + for file in core_new_files: + try: + rel = file.relative_to(self._image_dir) + if len(rel.parts) > 1: + continue + except ValueError: + pass + new_files.add(file) + return new_files + + def _fix_records(self, new_files): + new_files.update(i for i in self._image_dir.iterdir()) + extra_record_data_str = self._records_text(sorted(new_files, key=str)) + with open(ensure_text(str(self._dist_info / "RECORD")), "wb") as file_handler: + file_handler.write(extra_record_data_str.encode("utf-8")) + + def build_image(self): + super(SymlinkPipInstall, self).build_image() + # protect the image by making it read only + set_tree(self._image_dir, S_IREAD | S_IRGRP | S_IROTH) + + def clear(self): + if self._image_dir.exists(): + safe_delete(self._image_dir) + super(SymlinkPipInstall, self).clear() diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/via_app_data.py b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/via_app_data.py new file mode 100644 index 0000000000000000000000000000000000000000..1afa7978c54ff862b476a782c85b3cb64a85a667 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/embed/via_app_data/via_app_data.py @@ -0,0 +1,139 @@ +"""Bootstrap""" +from __future__ import absolute_import, unicode_literals + +import logging +import sys +import traceback +from contextlib import contextmanager +from subprocess import CalledProcessError +from threading import Lock, Thread + +from virtualenv.info import fs_supports_symlink +from virtualenv.seed.embed.base_embed import BaseEmbed +from virtualenv.seed.wheels import get_wheel +from virtualenv.util.path import Path + +from .pip_install.copy import CopyPipInstall +from .pip_install.symlink import SymlinkPipInstall + + +class FromAppData(BaseEmbed): + def __init__(self, options): + super(FromAppData, self).__init__(options) + self.symlinks = options.symlink_app_data + + @classmethod + def add_parser_arguments(cls, parser, interpreter, app_data): + super(FromAppData, cls).add_parser_arguments(parser, interpreter, app_data) + can_symlink = app_data.transient is False and fs_supports_symlink() + parser.add_argument( + "--symlink-app-data", + dest="symlink_app_data", + action="store_true" if can_symlink else "store_false", + help="{} symlink the python packages from the app-data folder (requires seed pip>=19.3)".format( + "" if can_symlink else "not supported - ", + ), + default=False, + ) + + def run(self, creator): + if not self.enabled: + return + with self._get_seed_wheels(creator) as name_to_whl: + pip_version = name_to_whl["pip"].version_tuple if "pip" in name_to_whl else None + installer_class = self.installer_class(pip_version) + exceptions = {} + + def _install(name, wheel): + try: + logging.debug("install %s from wheel %s via %s", name, wheel, installer_class.__name__) + key = Path(installer_class.__name__) / wheel.path.stem + wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key) + installer = installer_class(wheel.path, creator, wheel_img) + parent = self.app_data.lock / wheel_img.parent + with parent.non_reentrant_lock_for_key(wheel_img.name): + if not installer.has_image(): + installer.build_image() + installer.install(creator.interpreter.version_info) + except Exception: # noqa + exceptions[name] = sys.exc_info() + + threads = list(Thread(target=_install, args=(n, w)) for n, w in name_to_whl.items()) + for thread in threads: + thread.start() + for thread in threads: + thread.join() + if exceptions: + messages = ["failed to build image {} because:".format(", ".join(exceptions.keys()))] + for value in exceptions.values(): + exc_type, exc_value, exc_traceback = value + messages.append("".join(traceback.format_exception(exc_type, exc_value, exc_traceback))) + raise RuntimeError("\n".join(messages)) + + @contextmanager + def _get_seed_wheels(self, creator): + name_to_whl, lock, fail = {}, Lock(), {} + + def _get(distribution, version): + for_py_version = creator.interpreter.version_release_str + failure, result = None, None + # fallback to download in case the exact version is not available + for download in [True] if self.download else [False, True]: + failure = None + try: + result = get_wheel( + distribution=distribution, + version=version, + for_py_version=for_py_version, + search_dirs=self.extra_search_dir, + download=download, + app_data=self.app_data, + do_periodic_update=self.periodic_update, + ) + if result is not None: + break + except Exception as exception: # noqa + logging.exception("fail") + failure = exception + if failure: + if isinstance(failure, CalledProcessError): + msg = "failed to download {}".format(distribution) + if version is not None: + msg += " version {}".format(version) + msg += ", pip download exit code {}".format(failure.returncode) + output = failure.output if sys.version_info < (3, 5) else (failure.output + failure.stderr) + if output: + msg += "\n" + msg += output + else: + msg = repr(failure) + logging.error(msg) + with lock: + fail[distribution] = version + else: + with lock: + name_to_whl[distribution] = result + + threads = list( + Thread(target=_get, args=(distribution, version)) + for distribution, version in self.distribution_to_versions().items() + ) + for thread in threads: + thread.start() + for thread in threads: + thread.join() + if fail: + raise RuntimeError("seed failed due to failing to download wheels {}".format(", ".join(fail.keys()))) + yield name_to_whl + + def installer_class(self, pip_version_tuple): + if self.symlinks and pip_version_tuple: + # symlink support requires pip 19.3+ + if pip_version_tuple >= (19, 3): + return SymlinkPipInstall + return CopyPipInstall + + def __unicode__(self): + base = super(FromAppData, self).__unicode__() + msg = ", via={}, app_data_dir={}".format("symlink" if self.symlinks else "copy", self.app_data) + return base[:-1] + msg + base[-1] diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/seeder.py b/robot/lib/python3.8/site-packages/virtualenv/seed/seeder.py new file mode 100644 index 0000000000000000000000000000000000000000..2bcccfc727c9de01fef8f431df3502809f369875 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/seeder.py @@ -0,0 +1,39 @@ +from __future__ import absolute_import, unicode_literals + +from abc import ABCMeta, abstractmethod + +from six import add_metaclass + + +@add_metaclass(ABCMeta) +class Seeder(object): + """A seeder will install some seed packages into a virtual environment.""" + + # noinspection PyUnusedLocal + def __init__(self, options, enabled): + """ + + :param options: the parsed options as defined within :meth:`add_parser_arguments` + :param enabled: a flag weather the seeder is enabled or not + """ + self.enabled = enabled + + @classmethod + def add_parser_arguments(cls, parser, interpreter, app_data): + """ + Add CLI arguments for this seed mechanisms. + + :param parser: the CLI parser + :param app_data: the CLI parser + :param interpreter: the interpreter this virtual environment is based of + """ + raise NotImplementedError + + @abstractmethod + def run(self, creator): + """Perform the seed operation. + + :param creator: the creator (based of :class:`virtualenv.create.creator.Creator`) we used to create this \ + virtual environment + """ + raise NotImplementedError diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..dbffe2e433314f3ade6f9de9312cd75be6ea2add --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/__init__.py @@ -0,0 +1,11 @@ +from __future__ import absolute_import, unicode_literals + +from .acquire import get_wheel, pip_wheel_env_run +from .util import Version, Wheel + +__all__ = ( + "get_wheel", + "pip_wheel_env_run", + "Version", + "Wheel", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/acquire.py b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/acquire.py new file mode 100644 index 0000000000000000000000000000000000000000..e63ecb67cf2ec4cb5ada35f6a9638acebf86e873 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/acquire.py @@ -0,0 +1,120 @@ +"""Bootstrap""" +from __future__ import absolute_import, unicode_literals + +import logging +import os +import sys +from operator import eq, lt + +from virtualenv.util.path import Path +from virtualenv.util.six import ensure_str +from virtualenv.util.subprocess import Popen, subprocess + +from .bundle import from_bundle +from .util import Version, Wheel, discover_wheels + + +def get_wheel(distribution, version, for_py_version, search_dirs, download, app_data, do_periodic_update): + """ + Get a wheel with the given distribution-version-for_py_version trio, by using the extra search dir + download + """ + # not all wheels are compatible with all python versions, so we need to py version qualify it + # 1. acquire from bundle + wheel = from_bundle(distribution, version, for_py_version, search_dirs, app_data, do_periodic_update) + + # 2. download from the internet + if version not in Version.non_version and download: + wheel = download_wheel( + distribution=distribution, + version_spec=Version.as_version_spec(version), + for_py_version=for_py_version, + search_dirs=search_dirs, + app_data=app_data, + to_folder=app_data.house, + ) + return wheel + + +def download_wheel(distribution, version_spec, for_py_version, search_dirs, app_data, to_folder): + to_download = "{}{}".format(distribution, version_spec or "") + logging.debug("download wheel %s %s to %s", to_download, for_py_version, to_folder) + cmd = [ + sys.executable, + "-m", + "pip", + "download", + "--progress-bar", + "off", + "--disable-pip-version-check", + "--only-binary=:all:", + "--no-deps", + "--python-version", + for_py_version, + "-d", + str(to_folder), + to_download, + ] + # pip has no interface in python - must be a new sub-process + env = pip_wheel_env_run(search_dirs, app_data) + process = Popen(cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + out, err = process.communicate() + if process.returncode != 0: + kwargs = {"output": out} + if sys.version_info < (3, 5): + kwargs["output"] += err + else: + kwargs["stderr"] = err + raise subprocess.CalledProcessError(process.returncode, cmd, **kwargs) + result = _find_downloaded_wheel(distribution, version_spec, for_py_version, to_folder, out) + logging.debug("downloaded wheel %s", result.name) + return result + + +def _find_downloaded_wheel(distribution, version_spec, for_py_version, to_folder, out): + for line in out.splitlines(): + line = line.lstrip() + for marker in ("Saved ", "File was already downloaded "): + if line.startswith(marker): + return Wheel(Path(line[len(marker) :]).absolute()) + # if for some reason the output does not match fallback to latest version with that spec + return find_compatible_in_house(distribution, version_spec, for_py_version, to_folder) + + +def find_compatible_in_house(distribution, version_spec, for_py_version, in_folder): + wheels = discover_wheels(in_folder, distribution, None, for_py_version) + start, end = 0, len(wheels) + if version_spec is not None: + if version_spec.startswith("<"): + from_pos, op = 1, lt + elif version_spec.startswith("=="): + from_pos, op = 2, eq + else: + raise ValueError(version_spec) + version = Wheel.as_version_tuple(version_spec[from_pos:]) + start = next((at for at, w in enumerate(wheels) if op(w.version_tuple, version)), len(wheels)) + + return None if start == end else wheels[start] + + +def pip_wheel_env_run(search_dirs, app_data): + for_py_version = "{}.{}".format(*sys.version_info[0:2]) + env = os.environ.copy() + env.update( + { + ensure_str(k): str(v) # python 2 requires these to be string only (non-unicode) + for k, v in {"PIP_USE_WHEEL": "1", "PIP_USER": "0", "PIP_NO_INPUT": "1"}.items() + }, + ) + wheel = get_wheel( + distribution="pip", + version=None, + for_py_version=for_py_version, + search_dirs=search_dirs, + download=False, + app_data=app_data, + do_periodic_update=False, + ) + if wheel is None: + raise RuntimeError("could not find the embedded pip") + env[str("PYTHONPATH")] = str(wheel.path) + return env diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/bundle.py b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/bundle.py new file mode 100644 index 0000000000000000000000000000000000000000..7c664bd389a12399d28106a82522be36d29c9552 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/bundle.py @@ -0,0 +1,49 @@ +from __future__ import absolute_import, unicode_literals + +from ..wheels.embed import get_embed_wheel +from .periodic_update import periodic_update +from .util import Version, Wheel, discover_wheels + + +def from_bundle(distribution, version, for_py_version, search_dirs, app_data, do_periodic_update): + """ + Load the bundled wheel to a cache directory. + """ + of_version = Version.of_version(version) + wheel = load_embed_wheel(app_data, distribution, for_py_version, of_version) + + if version != Version.embed: + # 2. check if we have upgraded embed + if app_data.can_update: + wheel = periodic_update(distribution, for_py_version, wheel, search_dirs, app_data, do_periodic_update) + + # 3. acquire from extra search dir + found_wheel = from_dir(distribution, of_version, for_py_version, search_dirs) + if found_wheel is not None: + if wheel is None: + wheel = found_wheel + elif found_wheel.version_tuple > wheel.version_tuple: + wheel = found_wheel + return wheel + + +def load_embed_wheel(app_data, distribution, for_py_version, version): + wheel = get_embed_wheel(distribution, for_py_version) + if wheel is not None: + version_match = version == wheel.version + if version is None or version_match: + with app_data.ensure_extracted(wheel.path, lambda: app_data.house) as wheel_path: + wheel = Wheel(wheel_path) + else: # if version does not match ignore + wheel = None + return wheel + + +def from_dir(distribution, version, for_py_version, directories): + """ + Load a compatible wheel from a given folder. + """ + for folder in directories: + for wheel in discover_wheels(folder, distribution, version, for_py_version): + return wheel + return None diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5233e4876112daf015ff3c71bcdae7c9e007febd --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/__init__.py @@ -0,0 +1,62 @@ +from __future__ import absolute_import, unicode_literals + +from virtualenv.seed.wheels.util import Wheel +from virtualenv.util.path import Path + +BUNDLE_FOLDER = Path(__file__).absolute().parent +BUNDLE_SUPPORT = { + "3.10": { + "pip": "pip-20.3.1-py2.py3-none-any.whl", + "setuptools": "setuptools-51.0.0-py3-none-any.whl", + "wheel": "wheel-0.36.1-py2.py3-none-any.whl", + }, + "3.9": { + "pip": "pip-20.3.1-py2.py3-none-any.whl", + "setuptools": "setuptools-51.0.0-py3-none-any.whl", + "wheel": "wheel-0.36.1-py2.py3-none-any.whl", + }, + "3.8": { + "pip": "pip-20.3.1-py2.py3-none-any.whl", + "setuptools": "setuptools-51.0.0-py3-none-any.whl", + "wheel": "wheel-0.36.1-py2.py3-none-any.whl", + }, + "3.7": { + "pip": "pip-20.3.1-py2.py3-none-any.whl", + "setuptools": "setuptools-51.0.0-py3-none-any.whl", + "wheel": "wheel-0.36.1-py2.py3-none-any.whl", + }, + "3.6": { + "pip": "pip-20.3.1-py2.py3-none-any.whl", + "setuptools": "setuptools-51.0.0-py3-none-any.whl", + "wheel": "wheel-0.36.1-py2.py3-none-any.whl", + }, + "3.5": { + "pip": "pip-20.3.1-py2.py3-none-any.whl", + "setuptools": "setuptools-50.3.2-py3-none-any.whl", + "wheel": "wheel-0.36.1-py2.py3-none-any.whl", + }, + "3.4": { + "pip": "pip-19.1.1-py2.py3-none-any.whl", + "setuptools": "setuptools-43.0.0-py2.py3-none-any.whl", + "wheel": "wheel-0.33.6-py2.py3-none-any.whl", + }, + "2.7": { + "pip": "pip-20.3.1-py2.py3-none-any.whl", + "setuptools": "setuptools-44.1.1-py2.py3-none-any.whl", + "wheel": "wheel-0.36.1-py2.py3-none-any.whl", + }, +} +MAX = "3.10" + + +def get_embed_wheel(distribution, for_py_version): + path = BUNDLE_FOLDER / (BUNDLE_SUPPORT.get(for_py_version, {}) or BUNDLE_SUPPORT[MAX]).get(distribution) + return Wheel.from_path(path) + + +__all__ = ( + "get_embed_wheel", + "BUNDLE_SUPPORT", + "MAX", + "BUNDLE_FOLDER", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/pip-19.1.1-py2.py3-none-any.whl b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/pip-19.1.1-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..8476c119301f5ea25729bdadb47c716534e1e8c2 Binary files /dev/null and b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/pip-19.1.1-py2.py3-none-any.whl differ diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/pip-20.3.1-py2.py3-none-any.whl b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/pip-20.3.1-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..fbac5d3c90f33aadec124001a0e29ef091a923ab Binary files /dev/null and b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/pip-20.3.1-py2.py3-none-any.whl differ diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/setuptools-43.0.0-py2.py3-none-any.whl b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/setuptools-43.0.0-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..733faa6a546ddea979815b05e12c83fdd0bac706 Binary files /dev/null and b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/setuptools-43.0.0-py2.py3-none-any.whl differ diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/setuptools-44.1.1-py2.py3-none-any.whl b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/setuptools-44.1.1-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..bf28513c99b641da774c09877b977fa5a88518a9 Binary files /dev/null and b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/setuptools-44.1.1-py2.py3-none-any.whl differ diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/setuptools-50.3.2-py3-none-any.whl b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/setuptools-50.3.2-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..56d1bf92d7c5fbd27e052f948b3593ac1ecaf23c Binary files /dev/null and b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/setuptools-50.3.2-py3-none-any.whl differ diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/setuptools-51.0.0-py3-none-any.whl b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/setuptools-51.0.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..7e60e1130595ceeb033c4fde205c3c1cbbd7680e Binary files /dev/null and b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/setuptools-51.0.0-py3-none-any.whl differ diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/wheel-0.33.6-py2.py3-none-any.whl b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/wheel-0.33.6-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..2a71896be974b5cf20add0f91b9252735bd4a517 Binary files /dev/null and b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/wheel-0.33.6-py2.py3-none-any.whl differ diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/wheel-0.36.1-py2.py3-none-any.whl b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/wheel-0.36.1-py2.py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..1f17303bf91e546db738bd4f7434d735df091b60 Binary files /dev/null and b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/embed/wheel-0.36.1-py2.py3-none-any.whl differ diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/periodic_update.py b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/periodic_update.py new file mode 100644 index 0000000000000000000000000000000000000000..fd0ff4c264ab7dc5df007e94ecd3dc18cf418d35 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/periodic_update.py @@ -0,0 +1,367 @@ +""" +Periodically update bundled versions. +""" + +from __future__ import absolute_import, unicode_literals + +import json +import logging +import os +import ssl +import subprocess +import sys +from datetime import datetime, timedelta +from itertools import groupby +from shutil import copy2 +from textwrap import dedent +from threading import Thread + +from six.moves.urllib.error import URLError +from six.moves.urllib.request import urlopen + +from virtualenv.app_data import AppDataDiskFolder +from virtualenv.info import PY2 +from virtualenv.util.path import Path +from virtualenv.util.subprocess import CREATE_NO_WINDOW, Popen + +from ..wheels.embed import BUNDLE_SUPPORT +from ..wheels.util import Wheel + +if PY2: + # on Python 2 datetime.strptime throws the error below if the import did not trigger on main thread + # Failed to import _strptime because the import lock is held by + try: + import _strptime # noqa + except ImportError: # pragma: no cov + pass # pragma: no cov + + +def periodic_update(distribution, for_py_version, wheel, search_dirs, app_data, do_periodic_update): + if do_periodic_update: + handle_auto_update(distribution, for_py_version, wheel, search_dirs, app_data) + + now = datetime.now() + + u_log = UpdateLog.from_app_data(app_data, distribution, for_py_version) + u_log_older_than_hour = now - u_log.completed > timedelta(hours=1) if u_log.completed is not None else False + for _, group in groupby(u_log.versions, key=lambda v: v.wheel.version_tuple[0:2]): + version = next(group) # use only latest patch version per minor, earlier assumed to be buggy + if wheel is not None and Path(version.filename).name == wheel.name: + break + if u_log.periodic is False or (u_log_older_than_hour and version.use(now)): + updated_wheel = Wheel(app_data.house / version.filename) + logging.debug("using %supdated wheel %s", "periodically " if updated_wheel else "", updated_wheel) + wheel = updated_wheel + break + + return wheel + + +def handle_auto_update(distribution, for_py_version, wheel, search_dirs, app_data): + embed_update_log = app_data.embed_update_log(distribution, for_py_version) + u_log = UpdateLog.from_dict(embed_update_log.read()) + if u_log.needs_update: + u_log.periodic = True + u_log.started = datetime.now() + embed_update_log.write(u_log.to_dict()) + trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, periodic=True) + + +DATETIME_FMT = "%Y-%m-%dT%H:%M:%S.%fZ" + + +def dump_datetime(value): + return None if value is None else value.strftime(DATETIME_FMT) + + +def load_datetime(value): + return None if value is None else datetime.strptime(value, DATETIME_FMT) + + +class NewVersion(object): + def __init__(self, filename, found_date, release_date): + self.filename = filename + self.found_date = found_date + self.release_date = release_date + + @classmethod + def from_dict(cls, dictionary): + return cls( + filename=dictionary["filename"], + found_date=load_datetime(dictionary["found_date"]), + release_date=load_datetime(dictionary["release_date"]), + ) + + def to_dict(self): + return { + "filename": self.filename, + "release_date": dump_datetime(self.release_date), + "found_date": dump_datetime(self.found_date), + } + + def use(self, now): + compare_from = self.release_date or self.found_date + return now - compare_from >= timedelta(days=28) + + def __repr__(self): + return "{}(filename={}), found_date={}, release_date={})".format( + self.__class__.__name__, + self.filename, + self.found_date, + self.release_date, + ) + + def __eq__(self, other): + return type(self) == type(other) and all( + getattr(self, k) == getattr(other, k) for k in ["filename", "release_date", "found_date"] + ) + + def __ne__(self, other): + return not (self == other) + + @property + def wheel(self): + return Wheel(Path(self.filename)) + + +class UpdateLog(object): + def __init__(self, started, completed, versions, periodic): + self.started = started + self.completed = completed + self.versions = versions + self.periodic = periodic + + @classmethod + def from_dict(cls, dictionary): + if dictionary is None: + dictionary = {} + return cls( + load_datetime(dictionary.get("started")), + load_datetime(dictionary.get("completed")), + [NewVersion.from_dict(v) for v in dictionary.get("versions", [])], + dictionary.get("periodic"), + ) + + @classmethod + def from_app_data(cls, app_data, distribution, for_py_version): + raw_json = app_data.embed_update_log(distribution, for_py_version).read() + return cls.from_dict(raw_json) + + def to_dict(self): + return { + "started": dump_datetime(self.started), + "completed": dump_datetime(self.completed), + "periodic": self.periodic, + "versions": [r.to_dict() for r in self.versions], + } + + @property + def needs_update(self): + now = datetime.now() + if self.completed is None: # never completed + return self._check_start(now) + else: + if now - self.completed <= timedelta(days=14): + return False + return self._check_start(now) + + def _check_start(self, now): + return self.started is None or now - self.started > timedelta(hours=1) + + +def trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, periodic): + wheel_path = None if wheel is None else str(wheel.path) + cmd = [ + sys.executable, + "-c", + dedent( + """ + from virtualenv.report import setup_report, MAX_LEVEL + from virtualenv.seed.wheels.periodic_update import do_update + setup_report(MAX_LEVEL, show_pid=True) + do_update({!r}, {!r}, {!r}, {!r}, {!r}, {!r}) + """, + ) + .strip() + .format(distribution, for_py_version, wheel_path, str(app_data), [str(p) for p in search_dirs], periodic), + ] + debug = os.environ.get(str("_VIRTUALENV_PERIODIC_UPDATE_INLINE")) == str("1") + pipe = None if debug else subprocess.PIPE + kwargs = {"stdout": pipe, "stderr": pipe} + if not debug and sys.platform == "win32": + kwargs["creationflags"] = CREATE_NO_WINDOW + process = Popen(cmd, **kwargs) + logging.info( + "triggered periodic upgrade of %s%s (for python %s) via background process having PID %d", + distribution, + "" if wheel is None else "=={}".format(wheel.version), + for_py_version, + process.pid, + ) + if debug: + process.communicate() # on purpose not called to make it a background process + + +def do_update(distribution, for_py_version, embed_filename, app_data, search_dirs, periodic): + versions = None + try: + versions = _run_do_update(app_data, distribution, embed_filename, for_py_version, periodic, search_dirs) + finally: + logging.debug("done %s %s with %s", distribution, for_py_version, versions) + return versions + + +def _run_do_update(app_data, distribution, embed_filename, for_py_version, periodic, search_dirs): + from virtualenv.seed.wheels import acquire + + wheel_filename = None if embed_filename is None else Path(embed_filename) + embed_version = None if wheel_filename is None else Wheel(wheel_filename).version_tuple + app_data = AppDataDiskFolder(app_data) if isinstance(app_data, str) else app_data + search_dirs = [Path(p) if isinstance(p, str) else p for p in search_dirs] + wheelhouse = app_data.house + embed_update_log = app_data.embed_update_log(distribution, for_py_version) + u_log = UpdateLog.from_dict(embed_update_log.read()) + now = datetime.now() + if wheel_filename is not None: + dest = wheelhouse / wheel_filename.name + if not dest.exists(): + copy2(str(wheel_filename), str(wheelhouse)) + last, last_version, versions = None, None, [] + while last is None or not last.use(now): + download_time = datetime.now() + dest = acquire.download_wheel( + distribution=distribution, + version_spec=None if last_version is None else "<{}".format(last_version), + for_py_version=for_py_version, + search_dirs=search_dirs, + app_data=app_data, + to_folder=wheelhouse, + ) + if dest is None or (u_log.versions and u_log.versions[0].filename == dest.name): + break + release_date = release_date_for_wheel_path(dest.path) + last = NewVersion(filename=dest.path.name, release_date=release_date, found_date=download_time) + logging.info("detected %s in %s", last, datetime.now() - download_time) + versions.append(last) + last_wheel = Wheel(Path(last.filename)) + last_version = last_wheel.version + if embed_version is not None: + if embed_version >= last_wheel.version_tuple: # stop download if we reach the embed version + break + u_log.periodic = periodic + if not u_log.periodic: + u_log.started = now + u_log.versions = versions + u_log.versions + u_log.completed = datetime.now() + embed_update_log.write(u_log.to_dict()) + return versions + + +def release_date_for_wheel_path(dest): + wheel = Wheel(dest) + # the most accurate is to ask PyPi - e.g. https://pypi.org/pypi/pip/json, + # see https://warehouse.pypa.io/api-reference/json/ for more details + content = _pypi_get_distribution_info_cached(wheel.distribution) + if content is not None: + try: + upload_time = content["releases"][wheel.version][0]["upload_time"] + return datetime.strptime(upload_time, "%Y-%m-%dT%H:%M:%S") + except Exception as exception: + logging.error("could not load release date %s because %r", content, exception) + return None + + +def _request_context(): + yield None + # fallback to non verified HTTPS (the information we request is not sensitive, so fallback) + yield ssl._create_unverified_context() # noqa + + +_PYPI_CACHE = {} + + +def _pypi_get_distribution_info_cached(distribution): + if distribution not in _PYPI_CACHE: + _PYPI_CACHE[distribution] = _pypi_get_distribution_info(distribution) + return _PYPI_CACHE[distribution] + + +def _pypi_get_distribution_info(distribution): + content, url = None, "https://pypi.org/pypi/{}/json".format(distribution) + try: + for context in _request_context(): + try: + with urlopen(url, context=context) as file_handler: + content = json.load(file_handler) + break + except URLError as exception: + logging.error("failed to access %s because %r", url, exception) + except Exception as exception: + logging.error("failed to access %s because %r", url, exception) + return content + + +def manual_upgrade(app_data): + threads = [] + + for for_py_version, distribution_to_package in BUNDLE_SUPPORT.items(): + # load extra search dir for the given for_py + for distribution in distribution_to_package.keys(): + thread = Thread(target=_run_manual_upgrade, args=(app_data, distribution, for_py_version)) + thread.start() + threads.append(thread) + + for thread in threads: + thread.join() + + +def _run_manual_upgrade(app_data, distribution, for_py_version): + start = datetime.now() + from .bundle import from_bundle + + current = from_bundle( + distribution=distribution, + version=None, + for_py_version=for_py_version, + search_dirs=[], + app_data=app_data, + do_periodic_update=False, + ) + logging.warning( + "upgrade %s for python %s with current %s", + distribution, + for_py_version, + "" if current is None else current.name, + ) + versions = do_update( + distribution=distribution, + for_py_version=for_py_version, + embed_filename=current.path, + app_data=app_data, + search_dirs=[], + periodic=False, + ) + msg = "upgraded %s for python %s in %s {}".format( + "new entries found:\n%s" if versions else "no new versions found", + ) + args = [ + distribution, + for_py_version, + datetime.now() - start, + ] + if versions: + args.append("\n".join("\t{}".format(v) for v in versions)) + logging.warning(msg, *args) + + +__all__ = ( + "periodic_update", + "do_update", + "manual_upgrade", + "NewVersion", + "UpdateLog", + "load_datetime", + "dump_datetime", + "trigger_update", + "release_date_for_wheel_path", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/util.py b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/util.py new file mode 100644 index 0000000000000000000000000000000000000000..1240eb2d2439bbf0b64492083de1421a93417ea1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/seed/wheels/util.py @@ -0,0 +1,116 @@ +from __future__ import absolute_import, unicode_literals + +from operator import attrgetter +from zipfile import ZipFile + +from virtualenv.util.six import ensure_text + + +class Wheel(object): + def __init__(self, path): + # https://www.python.org/dev/peps/pep-0427/#file-name-convention + # The wheel filename is {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl + self.path = path + self._parts = path.stem.split("-") + + @classmethod + def from_path(cls, path): + if path is not None and path.suffix == ".whl" and len(path.stem.split("-")) >= 5: + return cls(path) + return None + + @property + def distribution(self): + return self._parts[0] + + @property + def version(self): + return self._parts[1] + + @property + def version_tuple(self): + return self.as_version_tuple(self.version) + + @staticmethod + def as_version_tuple(version): + result = [] + for part in version.split(".")[0:3]: + try: + result.append(int(part)) + except ValueError: + break + if not result: + raise ValueError(version) + return tuple(result) + + @property + def name(self): + return self.path.name + + def support_py(self, py_version): + name = "{}.dist-info/METADATA".format("-".join(self.path.stem.split("-")[0:2])) + with ZipFile(ensure_text(str(self.path)), "r") as zip_file: + metadata = zip_file.read(name).decode("utf-8") + marker = "Requires-Python:" + requires = next((i[len(marker) :] for i in metadata.splitlines() if i.startswith(marker)), None) + if requires is None: # if it does not specify a python requires the assumption is compatible + return True + py_version_int = tuple(int(i) for i in py_version.split(".")) + for require in (i.strip() for i in requires.split(",")): + # https://www.python.org/dev/peps/pep-0345/#version-specifiers + for operator, check in [ + ("!=", lambda v: py_version_int != v), + ("==", lambda v: py_version_int == v), + ("<=", lambda v: py_version_int <= v), + (">=", lambda v: py_version_int >= v), + ("<", lambda v: py_version_int < v), + (">", lambda v: py_version_int > v), + ]: + if require.startswith(operator): + ver_str = require[len(operator) :].strip() + version = tuple((int(i) if i != "*" else None) for i in ver_str.split("."))[0:2] + if not check(version): + return False + break + return True + + def __repr__(self): + return "{}({})".format(self.__class__.__name__, self.path) + + def __str__(self): + return str(self.path) + + +def discover_wheels(from_folder, distribution, version, for_py_version): + wheels = [] + for filename in from_folder.iterdir(): + wheel = Wheel.from_path(filename) + if wheel and wheel.distribution == distribution: + if version is None or wheel.version == version: + if wheel.support_py(for_py_version): + wheels.append(wheel) + return sorted(wheels, key=attrgetter("version_tuple", "distribution"), reverse=True) + + +class Version: + #: the version bundled with virtualenv + bundle = "bundle" + embed = "embed" + #: custom version handlers + non_version = ( + bundle, + embed, + ) + + @staticmethod + def of_version(value): + return None if value in Version.non_version else value + + @staticmethod + def as_pip_req(distribution, version): + return "{}{}".format(distribution, Version.as_version_spec(version)) + + @staticmethod + def as_version_spec(version): + of_version = Version.of_version(version) + return "" if of_version is None else "=={}".format(of_version) diff --git a/robot/lib/python3.8/site-packages/virtualenv/util/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/util/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..32d02925bf1819e7cb7f1e9687f8a1a923a81a77 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/util/__init__.py @@ -0,0 +1,11 @@ +from __future__ import absolute_import, unicode_literals + +import sys + +if sys.version_info[0] == 3: + import configparser as ConfigParser +else: + import ConfigParser + + +__all__ = ("ConfigParser",) diff --git a/robot/lib/python3.8/site-packages/virtualenv/util/error.py b/robot/lib/python3.8/site-packages/virtualenv/util/error.py new file mode 100644 index 0000000000000000000000000000000000000000..ac5aa502ddb9a4270946abf77448bac420cca11d --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/util/error.py @@ -0,0 +1,13 @@ +"""Errors""" +from __future__ import absolute_import, unicode_literals + + +class ProcessCallFailed(RuntimeError): + """Failed a process call""" + + def __init__(self, code, out, err, cmd): + super(ProcessCallFailed, self).__init__(code, out, err, cmd) + self.code = code + self.out = out + self.err = err + self.cmd = cmd diff --git a/robot/lib/python3.8/site-packages/virtualenv/util/lock.py b/robot/lib/python3.8/site-packages/virtualenv/util/lock.py new file mode 100644 index 0000000000000000000000000000000000000000..739dc5af80394f270d9a84efec35780954e312dc --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/util/lock.py @@ -0,0 +1,168 @@ +"""holds locking functionality that works across processes""" +from __future__ import absolute_import, unicode_literals + +import logging +import os +from abc import ABCMeta, abstractmethod +from contextlib import contextmanager +from threading import Lock, RLock + +from filelock import FileLock, Timeout +from six import add_metaclass + +from virtualenv.util.path import Path + + +class _CountedFileLock(FileLock): + def __init__(self, lock_file): + parent = os.path.dirname(lock_file) + if not os.path.isdir(parent): + try: + os.makedirs(parent) + except OSError: + pass + super(_CountedFileLock, self).__init__(lock_file) + self.count = 0 + self.thread_safe = RLock() + + def acquire(self, timeout=None, poll_intervall=0.05): + with self.thread_safe: + if self.count == 0: + super(_CountedFileLock, self).acquire(timeout=timeout, poll_intervall=poll_intervall) + self.count += 1 + + def release(self, force=False): + with self.thread_safe: + if self.count == 1: + super(_CountedFileLock, self).release(force=force) + self.count = max(self.count - 1, 0) + + +_lock_store = {} +_store_lock = Lock() + + +@add_metaclass(ABCMeta) +class PathLockBase(object): + def __init__(self, folder): + path = Path(folder) + self.path = path.resolve() if path.exists() else path + + def __repr__(self): + return "{}({})".format(self.__class__.__name__, self.path) + + def __div__(self, other): + return type(self)(self.path / other) + + def __truediv__(self, other): + return self.__div__(other) + + @abstractmethod + def __enter__(self): + raise NotImplementedError + + @abstractmethod + def __exit__(self, exc_type, exc_val, exc_tb): + raise NotImplementedError + + @abstractmethod + @contextmanager + def lock_for_key(self, name, no_block=False): + raise NotImplementedError + + @abstractmethod + @contextmanager + def non_reentrant_lock_for_key(name): + raise NotImplementedError + + +class ReentrantFileLock(PathLockBase): + def __init__(self, folder): + super(ReentrantFileLock, self).__init__(folder) + self._lock = None + + def _create_lock(self, name=""): + lock_file = str(self.path / "{}.lock".format(name)) + with _store_lock: + if lock_file not in _lock_store: + _lock_store[lock_file] = _CountedFileLock(lock_file) + return _lock_store[lock_file] + + @staticmethod + def _del_lock(lock): + with _store_lock: + if lock is not None: + with lock.thread_safe: + if lock.count == 0: + _lock_store.pop(lock.lock_file, None) + + def __del__(self): + self._del_lock(self._lock) + + def __enter__(self): + self._lock = self._create_lock() + self._lock_file(self._lock) + + def __exit__(self, exc_type, exc_val, exc_tb): + self._release(self._lock) + + def _lock_file(self, lock, no_block=False): + # multiple processes might be trying to get a first lock... so we cannot check if this directory exist without + # a lock, but that lock might then become expensive, and it's not clear where that lock should live. + # Instead here we just ignore if we fail to create the directory. + try: + os.makedirs(str(self.path)) + except OSError: + pass + try: + lock.acquire(0.0001) + except Timeout: + if no_block: + raise + logging.debug("lock file %s present, will block until released", lock.lock_file) + lock.release() # release the acquire try from above + lock.acquire() + + @staticmethod + def _release(lock): + lock.release() + + @contextmanager + def lock_for_key(self, name, no_block=False): + lock = self._create_lock(name) + try: + try: + self._lock_file(lock, no_block) + yield + finally: + self._release(lock) + finally: + self._del_lock(lock) + + @contextmanager + def non_reentrant_lock_for_key(self, name): + with _CountedFileLock(str(self.path / "{}.lock".format(name))): + yield + + +class NoOpFileLock(PathLockBase): + def __enter__(self): + raise NotImplementedError + + def __exit__(self, exc_type, exc_val, exc_tb): + raise NotImplementedError + + @contextmanager + def lock_for_key(self, name, no_block=False): + yield + + @contextmanager + def non_reentrant_lock_for_key(self, name): + yield + + +__all__ = ( + "NoOpFileLock", + "ReentrantFileLock", + "Timeout", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/util/path/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/util/path/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a7f71634b53d14674c2fc5be7171b0883756daa0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/util/path/__init__.py @@ -0,0 +1,16 @@ +from __future__ import absolute_import, unicode_literals + +from ._pathlib import Path +from ._permission import make_exe, set_tree +from ._sync import copy, copytree, ensure_dir, safe_delete, symlink + +__all__ = ( + "ensure_dir", + "symlink", + "copy", + "copytree", + "Path", + "make_exe", + "set_tree", + "safe_delete", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/util/path/_pathlib/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/util/path/_pathlib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6bb045c2d8da153bf9106c1ba74c82399b2b7639 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/util/path/_pathlib/__init__.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, unicode_literals + +import sys + +import six + +if six.PY3: + from pathlib import Path + + if sys.version_info[0:2] == (3, 4): + # no read/write text on python3.4 + BuiltinPath = Path + + class Path(type(BuiltinPath())): + def read_text(self, encoding=None, errors=None): + """ + Open the file in text mode, read it, and close the file. + """ + with self.open(mode="r", encoding=encoding, errors=errors) as f: + return f.read() + + def read_bytes(self): + """ + Open the file in bytes mode, read it, and close the file. + """ + with self.open(mode="rb") as f: + return f.read() + + def write_text(self, data, encoding=None, errors=None): + """ + Open the file in text mode, write to it, and close the file. + """ + if not isinstance(data, str): + raise TypeError("data must be str, not %s" % data.__class__.__name__) + with self.open(mode="w", encoding=encoding, errors=errors) as f: + return f.write(data) + + def write_bytes(self, data): + """ + Open the file in bytes mode, write to it, and close the file. + """ + # type-check for the buffer interface before truncating the file + view = memoryview(data) + with self.open(mode="wb") as f: + return f.write(view) + + def mkdir(self, mode=0o777, parents=False, exist_ok=False): + try: + super(type(BuiltinPath()), self).mkdir(mode, parents) + except FileExistsError as exception: + if not exist_ok: + raise exception + + +else: + if sys.platform == "win32": + # workaround for https://github.com/mcmtroffaes/pathlib2/issues/56 + from .via_os_path import Path + else: + from pathlib2 import Path + + +__all__ = ("Path",) diff --git a/robot/lib/python3.8/site-packages/virtualenv/util/path/_pathlib/via_os_path.py b/robot/lib/python3.8/site-packages/virtualenv/util/path/_pathlib/via_os_path.py new file mode 100644 index 0000000000000000000000000000000000000000..ac78d4f00a5073e094a9b1af6ce84e94349b1c9d --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/util/path/_pathlib/via_os_path.py @@ -0,0 +1,148 @@ +from __future__ import absolute_import, unicode_literals + +import os +import platform +from contextlib import contextmanager + +from virtualenv.util.six import ensure_str, ensure_text + +IS_PYPY = platform.python_implementation() == "PyPy" + + +class Path(object): + def __init__(self, path): + if isinstance(path, Path): + _path = path._path + else: + _path = ensure_text(path) + if IS_PYPY: + _path = _path.encode("utf-8") + self._path = _path + + def __repr__(self): + return ensure_str("Path({})".format(ensure_text(self._path))) + + def __unicode__(self): + return ensure_text(self._path) + + def __str__(self): + return ensure_str(self._path) + + def __div__(self, other): + if isinstance(other, Path): + right = other._path + else: + right = ensure_text(other) + if IS_PYPY: + right = right.encode("utf-8") + return Path(os.path.join(self._path, right)) + + def __truediv__(self, other): + return self.__div__(other) + + def __eq__(self, other): + return self._path == (other._path if isinstance(other, Path) else None) + + def __ne__(self, other): + return not (self == other) + + def __hash__(self): + return hash(self._path) + + def exists(self): + return os.path.exists(self._path) + + @property + def parent(self): + return Path(os.path.abspath(os.path.join(self._path, os.path.pardir))) + + def resolve(self): + return Path(os.path.realpath(self._path)) + + @property + def name(self): + return os.path.basename(self._path) + + @property + def parts(self): + return self._path.split(os.sep) + + def is_file(self): + return os.path.isfile(self._path) + + def is_dir(self): + return os.path.isdir(self._path) + + def mkdir(self, parents=True, exist_ok=True): + try: + os.makedirs(self._path) + except OSError: + if not exist_ok: + raise + + def read_text(self, encoding="utf-8"): + return self.read_bytes().decode(encoding) + + def read_bytes(self): + with open(self._path, "rb") as file_handler: + return file_handler.read() + + def write_bytes(self, content): + with open(self._path, "wb") as file_handler: + file_handler.write(content) + + def write_text(self, text, encoding="utf-8"): + self.write_bytes(text.encode(encoding)) + + def iterdir(self): + for p in os.listdir(self._path): + yield Path(os.path.join(self._path, p)) + + @property + def suffix(self): + _, ext = os.path.splitext(self.name) + return ext + + @property + def stem(self): + base, _ = os.path.splitext(self.name) + return base + + @contextmanager + def open(self, mode="r"): + with open(self._path, mode) as file_handler: + yield file_handler + + @property + def parents(self): + result = [] + parts = self.parts + for i in range(len(parts) - 1): + result.append(Path(os.sep.join(parts[0 : i + 1]))) + return result[::-1] + + def unlink(self): + os.remove(self._path) + + def with_name(self, name): + return self.parent / name + + def is_symlink(self): + return os.path.islink(self._path) + + def relative_to(self, other): + if not self._path.startswith(other._path): + raise ValueError("{} does not start with {}".format(self._path, other._path)) + return Path(os.sep.join(self.parts[len(other.parts) :])) + + def stat(self): + return os.stat(self._path) + + def chmod(self, mode): + os.chmod(self._path, mode) + + def absolute(self): + return Path(os.path.abspath(self._path)) + + +__all__ = ("Path",) diff --git a/robot/lib/python3.8/site-packages/virtualenv/util/path/_permission.py b/robot/lib/python3.8/site-packages/virtualenv/util/path/_permission.py new file mode 100644 index 0000000000000000000000000000000000000000..73bb6e81a35620ae9d8ecbbefd94f57c5277a896 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/util/path/_permission.py @@ -0,0 +1,32 @@ +from __future__ import absolute_import, unicode_literals + +import os +from stat import S_IXGRP, S_IXOTH, S_IXUSR + +from virtualenv.util.six import ensure_text + + +def make_exe(filename): + original_mode = filename.stat().st_mode + levels = [S_IXUSR, S_IXGRP, S_IXOTH] + for at in range(len(levels), 0, -1): + try: + mode = original_mode + for level in levels[:at]: + mode |= level + filename.chmod(mode) + break + except OSError: + continue + + +def set_tree(folder, stat): + for root, _, files in os.walk(ensure_text(str(folder))): + for filename in files: + os.chmod(os.path.join(root, filename), stat) + + +__all__ = ( + "make_exe", + "set_tree", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/util/path/_sync.py b/robot/lib/python3.8/site-packages/virtualenv/util/path/_sync.py new file mode 100644 index 0000000000000000000000000000000000000000..c3d4af78aad68cb9933243d8f70cc6d580bb574c --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/util/path/_sync.py @@ -0,0 +1,98 @@ +from __future__ import absolute_import, unicode_literals + +import logging +import os +import shutil +from stat import S_IWUSR + +from six import PY2 + +from virtualenv.info import IS_CPYTHON, IS_WIN +from virtualenv.util.six import ensure_text + +if PY2 and IS_CPYTHON and IS_WIN: # CPython2 on Windows supports unicode paths if passed as unicode + + def norm(src): + return ensure_text(str(src)) + + +else: + norm = str + + +def ensure_dir(path): + if not path.exists(): + logging.debug("create folder %s", ensure_text(str(path))) + os.makedirs(norm(path)) + + +def ensure_safe_to_do(src, dest): + if src == dest: + raise ValueError("source and destination is the same {}".format(src)) + if not dest.exists(): + return + if dest.is_dir() and not dest.is_symlink(): + logging.debug("remove directory %s", dest) + safe_delete(dest) + else: + logging.debug("remove file %s", dest) + dest.unlink() + + +def symlink(src, dest): + ensure_safe_to_do(src, dest) + logging.debug("symlink %s", _Debug(src, dest)) + dest.symlink_to(src, target_is_directory=src.is_dir()) + + +def copy(src, dest): + ensure_safe_to_do(src, dest) + is_dir = src.is_dir() + method = copytree if is_dir else shutil.copy + logging.debug("copy %s", _Debug(src, dest)) + method(norm(src), norm(dest)) + + +def copytree(src, dest): + for root, _, files in os.walk(src): + dest_dir = os.path.join(dest, os.path.relpath(root, src)) + if not os.path.isdir(dest_dir): + os.makedirs(dest_dir) + for name in files: + src_f = os.path.join(root, name) + dest_f = os.path.join(dest_dir, name) + shutil.copy(src_f, dest_f) + + +def safe_delete(dest): + def onerror(func, path, exc_info): + if not os.access(path, os.W_OK): + os.chmod(path, S_IWUSR) + func(path) + else: + raise + + shutil.rmtree(ensure_text(str(dest)), ignore_errors=True, onerror=onerror) + + +class _Debug(object): + def __init__(self, src, dest): + self.src = src + self.dest = dest + + def __str__(self): + return "{}{} to {}".format( + "directory " if self.src.is_dir() else "", + ensure_text(str(self.src)), + ensure_text(str(self.dest)), + ) + + +__all__ = ( + "ensure_dir", + "symlink", + "copy", + "symlink", + "copytree", + "safe_delete", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/util/six.py b/robot/lib/python3.8/site-packages/virtualenv/util/six.py new file mode 100644 index 0000000000000000000000000000000000000000..16f1c6c95efee0d2236d78dcce9a55cc96b0e756 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/util/six.py @@ -0,0 +1,50 @@ +"""Backward compatibility layer with older version of six. + +This is used to avoid virtualenv requring a version of six newer than what +the system may have. +""" +from __future__ import absolute_import + +from six import PY2, PY3, binary_type, text_type + +try: + from six import ensure_text +except ImportError: + + def ensure_text(s, encoding="utf-8", errors="strict"): + """Coerce *s* to six.text_type. + For Python 2: + - `unicode` -> `unicode` + - `str` -> `unicode` + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if isinstance(s, binary_type): + return s.decode(encoding, errors) + elif isinstance(s, text_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + +try: + from six import ensure_str +except ImportError: + + def ensure_str(s, encoding="utf-8", errors="strict"): + """Coerce *s* to `str`. + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) + if PY2 and isinstance(s, text_type): + s = s.encode(encoding, errors) + elif PY3 and isinstance(s, binary_type): + s = s.decode(encoding, errors) + return s diff --git a/robot/lib/python3.8/site-packages/virtualenv/util/subprocess/__init__.py b/robot/lib/python3.8/site-packages/virtualenv/util/subprocess/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f5066268f88f9920c96e407c7ce61c5b822a08b7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/util/subprocess/__init__.py @@ -0,0 +1,40 @@ +from __future__ import absolute_import, unicode_literals + +import subprocess +import sys + +import six + +if six.PY2 and sys.platform == "win32": + from . import _win_subprocess + + Popen = _win_subprocess.Popen +else: + Popen = subprocess.Popen + + +CREATE_NO_WINDOW = 0x80000000 + + +def run_cmd(cmd): + try: + process = Popen( + cmd, + universal_newlines=True, + stdin=subprocess.PIPE, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + out, err = process.communicate() # input disabled + code = process.returncode + except OSError as os_error: + code, out, err = os_error.errno, "", os_error.strerror + return code, out, err + + +__all__ = ( + "subprocess", + "Popen", + "run_cmd", + "CREATE_NO_WINDOW", +) diff --git a/robot/lib/python3.8/site-packages/virtualenv/util/subprocess/_win_subprocess.py b/robot/lib/python3.8/site-packages/virtualenv/util/subprocess/_win_subprocess.py new file mode 100644 index 0000000000000000000000000000000000000000..4c4c5d0295294c6ed8bbcc12fd43921fd9a08d33 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/util/subprocess/_win_subprocess.py @@ -0,0 +1,175 @@ +# flake8: noqa +# fmt: off +## issue: https://bugs.python.org/issue19264 + +import ctypes +import os +import platform +import subprocess +from ctypes import Structure, WinError, byref, c_char_p, c_void_p, c_wchar, c_wchar_p, sizeof, windll +from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPVOID, LPWSTR, WORD + +import _subprocess + +## +## Types +## + +CREATE_UNICODE_ENVIRONMENT = 0x00000400 +LPCTSTR = c_char_p +LPTSTR = c_wchar_p +LPSECURITY_ATTRIBUTES = c_void_p +LPBYTE = ctypes.POINTER(BYTE) + +class STARTUPINFOW(Structure): + _fields_ = [ + ("cb", DWORD), ("lpReserved", LPWSTR), + ("lpDesktop", LPWSTR), ("lpTitle", LPWSTR), + ("dwX", DWORD), ("dwY", DWORD), + ("dwXSize", DWORD), ("dwYSize", DWORD), + ("dwXCountChars", DWORD), ("dwYCountChars", DWORD), + ("dwFillAtrribute", DWORD), ("dwFlags", DWORD), + ("wShowWindow", WORD), ("cbReserved2", WORD), + ("lpReserved2", LPBYTE), ("hStdInput", HANDLE), + ("hStdOutput", HANDLE), ("hStdError", HANDLE), + ] + +LPSTARTUPINFOW = ctypes.POINTER(STARTUPINFOW) + + +class PROCESS_INFORMATION(Structure): + _fields_ = [ + ("hProcess", HANDLE), ("hThread", HANDLE), + ("dwProcessId", DWORD), ("dwThreadId", DWORD), + ] + +LPPROCESS_INFORMATION = ctypes.POINTER(PROCESS_INFORMATION) + + +class DUMMY_HANDLE(ctypes.c_void_p): + + def __init__(self, *a, **kw): + super(DUMMY_HANDLE, self).__init__(*a, **kw) + self.closed = False + + def Close(self): + if not self.closed: + windll.kernel32.CloseHandle(self) + self.closed = True + + def __int__(self): + return self.value + + +CreateProcessW = windll.kernel32.CreateProcessW +CreateProcessW.argtypes = [ + LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, + LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, + LPSTARTUPINFOW, LPPROCESS_INFORMATION, +] +CreateProcessW.restype = BOOL + + +## +## Patched functions/classes +## + +def CreateProcess( + executable, args, _p_attr, _t_attr, + inherit_handles, creation_flags, env, cwd, + startup_info, +): + """Create a process supporting unicode executable and args for win32 + + Python implementation of CreateProcess using CreateProcessW for Win32 + + """ + + si = STARTUPINFOW( + dwFlags=startup_info.dwFlags, + wShowWindow=startup_info.wShowWindow, + cb=sizeof(STARTUPINFOW), + ## XXXvlab: not sure of the casting here to ints. + hStdInput=startup_info.hStdInput if startup_info.hStdInput is None else int(startup_info.hStdInput), + hStdOutput=startup_info.hStdOutput if startup_info.hStdOutput is None else int(startup_info.hStdOutput), + hStdError=startup_info.hStdError if startup_info.hStdError is None else int(startup_info.hStdError), + ) + + wenv = None + if env is not None: + ## LPCWSTR seems to be c_wchar_p, so let's say CWSTR is c_wchar + env = ( + unicode("").join([ + unicode("%s=%s\0") % (k, v) + for k, v in env.items() + ]) + ) + unicode("\0") + wenv = (c_wchar * len(env))() + wenv.value = env + + wcwd = None + if cwd is not None: + wcwd = unicode(cwd) + + pi = PROCESS_INFORMATION() + creation_flags |= CREATE_UNICODE_ENVIRONMENT + + if CreateProcessW( + executable, args, None, None, + inherit_handles, creation_flags, + wenv, wcwd, byref(si), byref(pi), + ): + return ( + DUMMY_HANDLE(pi.hProcess), DUMMY_HANDLE(pi.hThread), + pi.dwProcessId, pi.dwThreadId, + ) + raise WinError() + + +class Popen(subprocess.Popen): + """This superseeds Popen and corrects a bug in cPython 2.7 implem""" + + def _execute_child( + self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, to_close, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite, + ): + """Code from part of _execute_child from Python 2.7 (9fbb65e) + + There are only 2 little changes concerning the construction of + the the final string in shell mode: we preempt the creation of + the command string when shell is True, because original function + will try to encode unicode args which we want to avoid to be able to + sending it as-is to ``CreateProcess``. + + """ + if startupinfo is None: + startupinfo = subprocess.STARTUPINFO() + if not isinstance(args, subprocess.types.StringTypes): + args = [i if isinstance(i, bytes) else i.encode('utf-8') for i in args] + args = subprocess.list2cmdline(args) + if platform.python_implementation() == "CPython": + args = args.decode('utf-8') + startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = _subprocess.SW_HIDE + comspec = os.environ.get("COMSPEC", unicode("cmd.exe")) + if ( + _subprocess.GetVersion() >= 0x80000000 or + os.path.basename(comspec).lower() == "command.com" + ): + w9xpopen = self._find_w9xpopen() + args = unicode('"%s" %s') % (w9xpopen, args) + creationflags |= _subprocess.CREATE_NEW_CONSOLE + + super(Popen, self)._execute_child( + args, executable, + preexec_fn, close_fds, cwd, env, universal_newlines, + startupinfo, creationflags, False, to_close, p2cread, + p2cwrite, c2pread, c2pwrite, errread, errwrite, + ) + +_subprocess.CreateProcess = CreateProcess +# fmt: on diff --git a/robot/lib/python3.8/site-packages/virtualenv/util/zipapp.py b/robot/lib/python3.8/site-packages/virtualenv/util/zipapp.py new file mode 100644 index 0000000000000000000000000000000000000000..85d9294f4df146b5b34656cf0c6042f5ea0e4ad3 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/util/zipapp.py @@ -0,0 +1,33 @@ +from __future__ import absolute_import, unicode_literals + +import logging +import os +import zipfile + +from virtualenv.info import IS_WIN, ROOT +from virtualenv.util.six import ensure_text + + +def read(full_path): + sub_file = _get_path_within_zip(full_path) + with zipfile.ZipFile(ROOT, "r") as zip_file: + with zip_file.open(sub_file) as file_handler: + return file_handler.read().decode("utf-8") + + +def extract(full_path, dest): + logging.debug("extract %s to %s", full_path, dest) + sub_file = _get_path_within_zip(full_path) + with zipfile.ZipFile(ROOT, "r") as zip_file: + info = zip_file.getinfo(sub_file) + info.filename = dest.name + zip_file.extract(info, ensure_text(str(dest.parent))) + + +def _get_path_within_zip(full_path): + full_path = os.path.abspath(str(full_path)) + sub_file = full_path[len(ROOT) + 1 :] + if IS_WIN: + # paths are always UNIX separators, even on Windows, though __file__ still follows platform default + sub_file = sub_file.replace(os.sep, "/") + return sub_file diff --git a/robot/lib/python3.8/site-packages/virtualenv/version.py b/robot/lib/python3.8/site-packages/virtualenv/version.py new file mode 100644 index 0000000000000000000000000000000000000000..9933ebc89f703ecea86e152356cdd9855b58b340 --- /dev/null +++ b/robot/lib/python3.8/site-packages/virtualenv/version.py @@ -0,0 +1,3 @@ +from __future__ import unicode_literals + +__version__ = "20.3.0" diff --git a/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/AUTHORS.txt b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/AUTHORS.txt new file mode 100644 index 0000000000000000000000000000000000000000..72c87d7d38ae7bf859717c333a5ee8230f6ce624 --- /dev/null +++ b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/AUTHORS.txt @@ -0,0 +1,562 @@ +A_Rog +Aakanksha Agrawal <11389424+rasponic@users.noreply.github.com> +Abhinav Sagar <40603139+abhinavsagar@users.noreply.github.com> +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Tse +Adam Wentz +admin +Adrien Morison +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Aleks Bunin +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Alexey Popravka +Alli +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrey Bulgakov +Andrés Delfino <34587441+andresdelfino@users.noreply.github.com> +Andrés Delfino +Andy Freeland +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anuj Godase +AQNOUCH Mohammed +AraHaan +Arindam Choudhury +Armin Ronacher +Artem +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avner Cohen +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Darnell +Ben Hoyt +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Brandon L. Reiss +Brandt Bucher +Brett Randall +Brian Cristante <33549821+brcrista@users.noreply.github.com> +Brian Cristante +Brian Rosner +BrownTruck +Bruno Oliveira +Bruno Renié +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +c22 +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Chih-Hsuan Yen +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris McDonough +Chris Wolfe +Christian Heimes +Christian Oudard +Christopher Hunt +Christopher Snyder +Clark Boylan +Clay McClure +Cody +Cody Soyland +Colin Watson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Curtis Doty +cytolentino +Damian Quiroga +Dan Black +Dan Savilonis +Dan Sully +daniel +Daniel Collins +Daniel Hahler +Daniel Holth +Daniel Jost +Daniel Shaulov +Daniele Esposti +Daniele Procida +Danny Hermes +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Bordeynik +David Caro +David Evans +David Linke +David Pursehouse +David Tucker +David Wales +Davidovich +derwolfe +Desetude +Diego Caraballo +DiegoCaraballo +Dmitry Gladkov +Domen Kožar +Donald Stufft +Dongweiming +Douglas Thor +DrFeathers +Dustin Ingram +Dwayne Bailey +Ed Morley <501702+edmorley@users.noreply.github.com> +Ed Morley +Eitan Adler +ekristina +elainechan +Eli Schwartz +Eli Schwartz +Emil Burzo +Emil Styrke +Endoh Takanao +enoch +Erdinc Mutlu +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Ernest W Durbin III +Ernest W. Durbin III +Erwin Janssen +Eugene Vereshchagin +everdimension +Felix Yan +fiber-space +Filip Kokosiński +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gdanielson +Geoffrey Lehée +Geoffrey Sneddon +George Song +Georgi Valkov +Giftlin Rajaiah +gizmoguy1 +gkdoc <40815324+gkdoc@users.noreply.github.com> +Gopinath M <31352222+mgopi1990@users.noreply.github.com> +GOTO Hayato <3532528+gh640@users.noreply.github.com> +gpiks +Guilherme Espada +Guy Rozendorn +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +Herbert Pfennig +Hsiaoming Yang +Hugo +Hugo Lopes Tavares +Hugo van Kemenade +hugovk +Hynek Schlawack +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ilya Baryshev +INADA Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Ivan Pozdeev +Jacob Kim +jakirkham +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James Cleveland +James Cleveland +James Firth +James Polley +Jan Pokorný +Jannis Leidel +jarondl +Jason R. Coombs +Jay Graves +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jelmer Vernooij +jenix21 +Jeremy Stanley +Jeremy Zafran +Jiashuo Li +Jim Garrison +Jivan Amara +John Paton +John-Scott Atlakson +johnthagen +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joost Molenaar +Jorge Niedbalski +Joseph Long +Josh Bronson +Josh Hansen +Josh Schneier +Juanjo Bazán +Julian Berman +Julian Gethmann +Julien Demoor +jwg4 +Jyrki Pulliainen +Kai Chen +Kamal Bin Mustafa +kaustav haldar +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kexuan Sun +Kit Randel +kpinc +Krishna Oza +Kumar McMillan +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurie Opperman +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +Loren Carvalho +Lucas Cimon +Ludovic Gasc +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +László Kiss Kollár +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark Williams +Mark Williams +Markus Hametner +Masaki +Masklinn +Matej Stuchlik +Mathew Jennings +Mathieu Bridon +Matt Good +Matt Maker +Matt Robenolt +matthew +Matthew Einhorn +Matthew Gilliard +Matthew Iversen +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna <44498973+mbaluna@users.noreply.github.com> +mdebi <17590103+mdebi@users.noreply.github.com> +memoselyk +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Williamson +michaelpacer +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Monica Baluna +montefra +Monty Taylor +Nate Coraor +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nikhil Benesch +Nitesh Sharma +Nowell Strite +NtaleGrey +nvdv +Ofekmeister +ofrinevo +Oliver Jeeves +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +OMOTO Kenji +Omry Yadan +Oren Held +Oscar Benjamin +Oz N Tiram +Pachwenko <32424503+Pachwenko@users.noreply.github.com> +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavithra Eswaramoorthy <33131404+QueenCoffee@users.noreply.github.com> +Pawel Jasinski +Pekka Klärck +Peter Lisák +Peter Waller +petr-tik +Phaneendra Chiruvella +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Pratik Mallya +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal <7895pulkit@gmail.com> +Qiangning Hong +Quentin Pradet +R. David Murray +Rafael Caricio +Ralf Schmitt +Razzi Abuissa +rdb +Remi Rampin +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Richard Jones +RobberPhex +Robert Collins +Robert McGibbon +Robert T. McGibbon +robin elisha robinson +Roey Berman +Rohan Jain +Rohan Jain +Rohan Jain +Roman Bogorodskiy +Romuald Brunet +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +Roy Wellington Ⅳ +Ryan Wooden +ryneeverett +Sachi King +Salvatore Rinchiera +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sergey Vasilyev +Seth Woodworth +Shlomi Fish +Shovan Maity +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +Sorin Sbarnea +Stavros Korokithakis +Stefan Scherfke +Stephan Erb +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +stonebig +Stéphane Bidoul (ACSONE) +Stéphane Bidoul +Stéphane Klein +Sumana Harihareswara +Sviatoslav Sydorenko +Sviatoslav Sydorenko +Swat009 +Takayuki SHIMIZUKAWA +tbeswick +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tom Forbes +Tom Freudenheim +Tom V +Tomas Orsava +Tomer Chachamu +Tony Beswick +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +Travis Swicegood +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Philippon +Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> +Vitaly Babiy +Vladimir Rutsky +W. Trevor King +Wil Tan +Wilfred Hughes +William ML Leslie +William T Olson +Wilson Mo +wim glenn +Wolfgang Maier +Xavier Fernandez +Xavier Fernandez +xoviat +xtreak +YAMAMOTO Takashi +Yen Chi Hsuan +Yeray Diaz Diaz +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Zearin +Zearin +Zhiping Deng +Zvezdan Petkovic +Łukasz Langa +Семён Марьясин diff --git a/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..737fec5c5352af3d9a6a47a0670da4bdb52c5725 --- /dev/null +++ b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2019 The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/METADATA b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..60fcd11874eb084e956c13a8780052dc93d358cb --- /dev/null +++ b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/METADATA @@ -0,0 +1,54 @@ +Metadata-Version: 2.1 +Name: webencodings +Version: 0.5.1 +Summary: Character encoding aliases for legacy web content +Home-page: https://github.com/SimonSapin/python-webencodings +Author: Simon Sapin +Author-email: simon.sapin@exyr.org +Maintainer: Geoffrey Sneddon +Maintainer-email: me@gsnedders.com +License: BSD +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: WWW/HTTP + +python-webencodings +=================== + +This is a Python implementation of the `WHATWG Encoding standard +`_. + +* Latest documentation: http://packages.python.org/webencodings/ +* Source code and issue tracker: + https://github.com/gsnedders/python-webencodings +* PyPI releases: http://pypi.python.org/pypi/webencodings +* License: BSD +* Python 2.6+ and 3.3+ + +In order to be compatible with legacy web content +when interpreting something like ``Content-Type: text/html; charset=latin1``, +tools need to use a particular set of aliases for encoding labels +as well as some overriding rules. +For example, ``US-ASCII`` and ``iso-8859-1`` on the web are actually +aliases for ``windows-1252``, and an UTF-8 or UTF-16 BOM takes precedence +over any other encoding declaration. +The Encoding standard defines all such details so that implementations do +not have to reverse-engineer each other. + +This module has encoding labels and BOM detection, +but the actual implementation for encoders and decoders is Python’s. + + diff --git a/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/RECORD b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..91e54a59c764dd4b655acd9a6c9c4aea0e074506 --- /dev/null +++ b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/RECORD @@ -0,0 +1,20 @@ +webencodings/__init__.py,sha256=qOBJIuPy_4ByYH6W_bNgJF-qYQ2DoU-dKsDu5yRWCXg,10579 +webencodings/labels.py,sha256=4AO_KxTddqGtrL9ns7kAPjb0CcN6xsCIxbK37HY9r3E,8979 +webencodings/mklabels.py,sha256=GYIeywnpaLnP0GSic8LFWgd0UVvO_l1Nc6YoF-87R_4,1305 +webencodings/tests.py,sha256=OtGLyjhNY1fvkW1GvLJ_FV9ZoqC9Anyjr7q3kxTbzNs,6563 +webencodings/x_user_defined.py,sha256=yOqWSdmpytGfUgh_Z6JYgDNhoc-BAHyyeeT15Fr42tM,4307 +webencodings-0.5.1.dist-info/AUTHORS.txt,sha256=RtqU9KfonVGhI48DAA4-yTOBUhBtQTjFhaDzHoyh7uU,21518 +webencodings-0.5.1.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090 +webencodings-0.5.1.dist-info/METADATA,sha256=3twdnuIvUN-fUitwJzXMpnqmyfbCHLd06nrbJ6vmD2w,2126 +webencodings-0.5.1.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +webencodings-0.5.1.dist-info/top_level.txt,sha256=bZs_aZHSf_PNlfIHD4-BETJmRi99BJdKLrOW7rQngeo,13 +webencodings-0.5.1.dist-info/RECORD,, +webencodings-0.5.1.virtualenv,, +webencodings/tests.cpython-38.pyc,, +webencodings/x_user_defined.cpython-38.pyc,, +webencodings-0.5.1.dist-info/__pycache__,, +webencodings/mklabels.cpython-38.pyc,, +webencodings/labels.cpython-38.pyc,, +webencodings/__init__.cpython-38.pyc,, +webencodings-0.5.1.dist-info/INSTALLER,, +webencodings/__pycache__,, \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/WHEEL b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ef99c6cf3283b50a273ac4c6d009a0aa85597070 --- /dev/null +++ b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..be8fcb779f88222d5b7a74327978e4d9dff94946 --- /dev/null +++ b/robot/lib/python3.8/site-packages/webencodings-0.5.1.dist-info/top_level.txt @@ -0,0 +1 @@ +webencodings diff --git a/robot/lib/python3.8/site-packages/webencodings-0.5.1.virtualenv b/robot/lib/python3.8/site-packages/webencodings-0.5.1.virtualenv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/webencodings/__init__.py b/robot/lib/python3.8/site-packages/webencodings/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d21d697c887bed1f8ab7f36d10185e986d9f1e54 --- /dev/null +++ b/robot/lib/python3.8/site-packages/webencodings/__init__.py @@ -0,0 +1,342 @@ +# coding: utf-8 +""" + + webencodings + ~~~~~~~~~~~~ + + This is a Python implementation of the `WHATWG Encoding standard + `. See README for details. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +from __future__ import unicode_literals + +import codecs + +from .labels import LABELS + + +VERSION = '0.5.1' + + +# Some names in Encoding are not valid Python aliases. Remap these. +PYTHON_NAMES = { + 'iso-8859-8-i': 'iso-8859-8', + 'x-mac-cyrillic': 'mac-cyrillic', + 'macintosh': 'mac-roman', + 'windows-874': 'cp874'} + +CACHE = {} + + +def ascii_lower(string): + r"""Transform (only) ASCII letters to lower case: A-Z is mapped to a-z. + + :param string: An Unicode string. + :returns: A new Unicode string. + + This is used for `ASCII case-insensitive + `_ + matching of encoding labels. + The same matching is also used, among other things, + for `CSS keywords `_. + + This is different from the :meth:`~py:str.lower` method of Unicode strings + which also affect non-ASCII characters, + sometimes mapping them into the ASCII range: + + >>> keyword = u'Bac\N{KELVIN SIGN}ground' + >>> assert keyword.lower() == u'background' + >>> assert ascii_lower(keyword) != keyword.lower() + >>> assert ascii_lower(keyword) == u'bac\N{KELVIN SIGN}ground' + + """ + # This turns out to be faster than unicode.translate() + return string.encode('utf8').lower().decode('utf8') + + +def lookup(label): + """ + Look for an encoding by its label. + This is the spec’s `get an encoding + `_ algorithm. + Supported labels are listed there. + + :param label: A string. + :returns: + An :class:`Encoding` object, or :obj:`None` for an unknown label. + + """ + # Only strip ASCII whitespace: U+0009, U+000A, U+000C, U+000D, and U+0020. + label = ascii_lower(label.strip('\t\n\f\r ')) + name = LABELS.get(label) + if name is None: + return None + encoding = CACHE.get(name) + if encoding is None: + if name == 'x-user-defined': + from .x_user_defined import codec_info + else: + python_name = PYTHON_NAMES.get(name, name) + # Any python_name value that gets to here should be valid. + codec_info = codecs.lookup(python_name) + encoding = Encoding(name, codec_info) + CACHE[name] = encoding + return encoding + + +def _get_encoding(encoding_or_label): + """ + Accept either an encoding object or label. + + :param encoding: An :class:`Encoding` object or a label string. + :returns: An :class:`Encoding` object. + :raises: :exc:`~exceptions.LookupError` for an unknown label. + + """ + if hasattr(encoding_or_label, 'codec_info'): + return encoding_or_label + + encoding = lookup(encoding_or_label) + if encoding is None: + raise LookupError('Unknown encoding label: %r' % encoding_or_label) + return encoding + + +class Encoding(object): + """Reresents a character encoding such as UTF-8, + that can be used for decoding or encoding. + + .. attribute:: name + + Canonical name of the encoding + + .. attribute:: codec_info + + The actual implementation of the encoding, + a stdlib :class:`~codecs.CodecInfo` object. + See :func:`codecs.register`. + + """ + def __init__(self, name, codec_info): + self.name = name + self.codec_info = codec_info + + def __repr__(self): + return '' % self.name + + +#: The UTF-8 encoding. Should be used for new content and formats. +UTF8 = lookup('utf-8') + +_UTF16LE = lookup('utf-16le') +_UTF16BE = lookup('utf-16be') + + +def decode(input, fallback_encoding, errors='replace'): + """ + Decode a single string. + + :param input: A byte string + :param fallback_encoding: + An :class:`Encoding` object or a label string. + The encoding to use if :obj:`input` does note have a BOM. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + :return: + A ``(output, encoding)`` tuple of an Unicode string + and an :obj:`Encoding`. + + """ + # Fail early if `encoding` is an invalid label. + fallback_encoding = _get_encoding(fallback_encoding) + bom_encoding, input = _detect_bom(input) + encoding = bom_encoding or fallback_encoding + return encoding.codec_info.decode(input, errors)[0], encoding + + +def _detect_bom(input): + """Return (bom_encoding, input), with any BOM removed from the input.""" + if input.startswith(b'\xFF\xFE'): + return _UTF16LE, input[2:] + if input.startswith(b'\xFE\xFF'): + return _UTF16BE, input[2:] + if input.startswith(b'\xEF\xBB\xBF'): + return UTF8, input[3:] + return None, input + + +def encode(input, encoding=UTF8, errors='strict'): + """ + Encode a single string. + + :param input: An Unicode string. + :param encoding: An :class:`Encoding` object or a label string. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + :return: A byte string. + + """ + return _get_encoding(encoding).codec_info.encode(input, errors)[0] + + +def iter_decode(input, fallback_encoding, errors='replace'): + """ + "Pull"-based decoder. + + :param input: + An iterable of byte strings. + + The input is first consumed just enough to determine the encoding + based on the precense of a BOM, + then consumed on demand when the return value is. + :param fallback_encoding: + An :class:`Encoding` object or a label string. + The encoding to use if :obj:`input` does note have a BOM. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + :returns: + An ``(output, encoding)`` tuple. + :obj:`output` is an iterable of Unicode strings, + :obj:`encoding` is the :obj:`Encoding` that is being used. + + """ + + decoder = IncrementalDecoder(fallback_encoding, errors) + generator = _iter_decode_generator(input, decoder) + encoding = next(generator) + return generator, encoding + + +def _iter_decode_generator(input, decoder): + """Return a generator that first yields the :obj:`Encoding`, + then yields output chukns as Unicode strings. + + """ + decode = decoder.decode + input = iter(input) + for chunck in input: + output = decode(chunck) + if output: + assert decoder.encoding is not None + yield decoder.encoding + yield output + break + else: + # Input exhausted without determining the encoding + output = decode(b'', final=True) + assert decoder.encoding is not None + yield decoder.encoding + if output: + yield output + return + + for chunck in input: + output = decode(chunck) + if output: + yield output + output = decode(b'', final=True) + if output: + yield output + + +def iter_encode(input, encoding=UTF8, errors='strict'): + """ + “Pull”-based encoder. + + :param input: An iterable of Unicode strings. + :param encoding: An :class:`Encoding` object or a label string. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + :returns: An iterable of byte strings. + + """ + # Fail early if `encoding` is an invalid label. + encode = IncrementalEncoder(encoding, errors).encode + return _iter_encode_generator(input, encode) + + +def _iter_encode_generator(input, encode): + for chunck in input: + output = encode(chunck) + if output: + yield output + output = encode('', final=True) + if output: + yield output + + +class IncrementalDecoder(object): + """ + “Push”-based decoder. + + :param fallback_encoding: + An :class:`Encoding` object or a label string. + The encoding to use if :obj:`input` does note have a BOM. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + + """ + def __init__(self, fallback_encoding, errors='replace'): + # Fail early if `encoding` is an invalid label. + self._fallback_encoding = _get_encoding(fallback_encoding) + self._errors = errors + self._buffer = b'' + self._decoder = None + #: The actual :class:`Encoding` that is being used, + #: or :obj:`None` if that is not determined yet. + #: (Ie. if there is not enough input yet to determine + #: if there is a BOM.) + self.encoding = None # Not known yet. + + def decode(self, input, final=False): + """Decode one chunk of the input. + + :param input: A byte string. + :param final: + Indicate that no more input is available. + Must be :obj:`True` if this is the last call. + :returns: An Unicode string. + + """ + decoder = self._decoder + if decoder is not None: + return decoder(input, final) + + input = self._buffer + input + encoding, input = _detect_bom(input) + if encoding is None: + if len(input) < 3 and not final: # Not enough data yet. + self._buffer = input + return '' + else: # No BOM + encoding = self._fallback_encoding + decoder = encoding.codec_info.incrementaldecoder(self._errors).decode + self._decoder = decoder + self.encoding = encoding + return decoder(input, final) + + +class IncrementalEncoder(object): + """ + “Push”-based encoder. + + :param encoding: An :class:`Encoding` object or a label string. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + + .. method:: encode(input, final=False) + + :param input: An Unicode string. + :param final: + Indicate that no more input is available. + Must be :obj:`True` if this is the last call. + :returns: A byte string. + + """ + def __init__(self, encoding=UTF8, errors='strict'): + encoding = _get_encoding(encoding) + self.encode = encoding.codec_info.incrementalencoder(errors).encode diff --git a/robot/lib/python3.8/site-packages/webencodings/labels.py b/robot/lib/python3.8/site-packages/webencodings/labels.py new file mode 100644 index 0000000000000000000000000000000000000000..29cbf91ef79b89971e51db9ddfc3720d8b4db82a --- /dev/null +++ b/robot/lib/python3.8/site-packages/webencodings/labels.py @@ -0,0 +1,231 @@ +""" + + webencodings.labels + ~~~~~~~~~~~~~~~~~~~ + + Map encoding labels to their name. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +# XXX Do not edit! +# This file is automatically generated by mklabels.py + +LABELS = { + 'unicode-1-1-utf-8': 'utf-8', + 'utf-8': 'utf-8', + 'utf8': 'utf-8', + '866': 'ibm866', + 'cp866': 'ibm866', + 'csibm866': 'ibm866', + 'ibm866': 'ibm866', + 'csisolatin2': 'iso-8859-2', + 'iso-8859-2': 'iso-8859-2', + 'iso-ir-101': 'iso-8859-2', + 'iso8859-2': 'iso-8859-2', + 'iso88592': 'iso-8859-2', + 'iso_8859-2': 'iso-8859-2', + 'iso_8859-2:1987': 'iso-8859-2', + 'l2': 'iso-8859-2', + 'latin2': 'iso-8859-2', + 'csisolatin3': 'iso-8859-3', + 'iso-8859-3': 'iso-8859-3', + 'iso-ir-109': 'iso-8859-3', + 'iso8859-3': 'iso-8859-3', + 'iso88593': 'iso-8859-3', + 'iso_8859-3': 'iso-8859-3', + 'iso_8859-3:1988': 'iso-8859-3', + 'l3': 'iso-8859-3', + 'latin3': 'iso-8859-3', + 'csisolatin4': 'iso-8859-4', + 'iso-8859-4': 'iso-8859-4', + 'iso-ir-110': 'iso-8859-4', + 'iso8859-4': 'iso-8859-4', + 'iso88594': 'iso-8859-4', + 'iso_8859-4': 'iso-8859-4', + 'iso_8859-4:1988': 'iso-8859-4', + 'l4': 'iso-8859-4', + 'latin4': 'iso-8859-4', + 'csisolatincyrillic': 'iso-8859-5', + 'cyrillic': 'iso-8859-5', + 'iso-8859-5': 'iso-8859-5', + 'iso-ir-144': 'iso-8859-5', + 'iso8859-5': 'iso-8859-5', + 'iso88595': 'iso-8859-5', + 'iso_8859-5': 'iso-8859-5', + 'iso_8859-5:1988': 'iso-8859-5', + 'arabic': 'iso-8859-6', + 'asmo-708': 'iso-8859-6', + 'csiso88596e': 'iso-8859-6', + 'csiso88596i': 'iso-8859-6', + 'csisolatinarabic': 'iso-8859-6', + 'ecma-114': 'iso-8859-6', + 'iso-8859-6': 'iso-8859-6', + 'iso-8859-6-e': 'iso-8859-6', + 'iso-8859-6-i': 'iso-8859-6', + 'iso-ir-127': 'iso-8859-6', + 'iso8859-6': 'iso-8859-6', + 'iso88596': 'iso-8859-6', + 'iso_8859-6': 'iso-8859-6', + 'iso_8859-6:1987': 'iso-8859-6', + 'csisolatingreek': 'iso-8859-7', + 'ecma-118': 'iso-8859-7', + 'elot_928': 'iso-8859-7', + 'greek': 'iso-8859-7', + 'greek8': 'iso-8859-7', + 'iso-8859-7': 'iso-8859-7', + 'iso-ir-126': 'iso-8859-7', + 'iso8859-7': 'iso-8859-7', + 'iso88597': 'iso-8859-7', + 'iso_8859-7': 'iso-8859-7', + 'iso_8859-7:1987': 'iso-8859-7', + 'sun_eu_greek': 'iso-8859-7', + 'csiso88598e': 'iso-8859-8', + 'csisolatinhebrew': 'iso-8859-8', + 'hebrew': 'iso-8859-8', + 'iso-8859-8': 'iso-8859-8', + 'iso-8859-8-e': 'iso-8859-8', + 'iso-ir-138': 'iso-8859-8', + 'iso8859-8': 'iso-8859-8', + 'iso88598': 'iso-8859-8', + 'iso_8859-8': 'iso-8859-8', + 'iso_8859-8:1988': 'iso-8859-8', + 'visual': 'iso-8859-8', + 'csiso88598i': 'iso-8859-8-i', + 'iso-8859-8-i': 'iso-8859-8-i', + 'logical': 'iso-8859-8-i', + 'csisolatin6': 'iso-8859-10', + 'iso-8859-10': 'iso-8859-10', + 'iso-ir-157': 'iso-8859-10', + 'iso8859-10': 'iso-8859-10', + 'iso885910': 'iso-8859-10', + 'l6': 'iso-8859-10', + 'latin6': 'iso-8859-10', + 'iso-8859-13': 'iso-8859-13', + 'iso8859-13': 'iso-8859-13', + 'iso885913': 'iso-8859-13', + 'iso-8859-14': 'iso-8859-14', + 'iso8859-14': 'iso-8859-14', + 'iso885914': 'iso-8859-14', + 'csisolatin9': 'iso-8859-15', + 'iso-8859-15': 'iso-8859-15', + 'iso8859-15': 'iso-8859-15', + 'iso885915': 'iso-8859-15', + 'iso_8859-15': 'iso-8859-15', + 'l9': 'iso-8859-15', + 'iso-8859-16': 'iso-8859-16', + 'cskoi8r': 'koi8-r', + 'koi': 'koi8-r', + 'koi8': 'koi8-r', + 'koi8-r': 'koi8-r', + 'koi8_r': 'koi8-r', + 'koi8-u': 'koi8-u', + 'csmacintosh': 'macintosh', + 'mac': 'macintosh', + 'macintosh': 'macintosh', + 'x-mac-roman': 'macintosh', + 'dos-874': 'windows-874', + 'iso-8859-11': 'windows-874', + 'iso8859-11': 'windows-874', + 'iso885911': 'windows-874', + 'tis-620': 'windows-874', + 'windows-874': 'windows-874', + 'cp1250': 'windows-1250', + 'windows-1250': 'windows-1250', + 'x-cp1250': 'windows-1250', + 'cp1251': 'windows-1251', + 'windows-1251': 'windows-1251', + 'x-cp1251': 'windows-1251', + 'ansi_x3.4-1968': 'windows-1252', + 'ascii': 'windows-1252', + 'cp1252': 'windows-1252', + 'cp819': 'windows-1252', + 'csisolatin1': 'windows-1252', + 'ibm819': 'windows-1252', + 'iso-8859-1': 'windows-1252', + 'iso-ir-100': 'windows-1252', + 'iso8859-1': 'windows-1252', + 'iso88591': 'windows-1252', + 'iso_8859-1': 'windows-1252', + 'iso_8859-1:1987': 'windows-1252', + 'l1': 'windows-1252', + 'latin1': 'windows-1252', + 'us-ascii': 'windows-1252', + 'windows-1252': 'windows-1252', + 'x-cp1252': 'windows-1252', + 'cp1253': 'windows-1253', + 'windows-1253': 'windows-1253', + 'x-cp1253': 'windows-1253', + 'cp1254': 'windows-1254', + 'csisolatin5': 'windows-1254', + 'iso-8859-9': 'windows-1254', + 'iso-ir-148': 'windows-1254', + 'iso8859-9': 'windows-1254', + 'iso88599': 'windows-1254', + 'iso_8859-9': 'windows-1254', + 'iso_8859-9:1989': 'windows-1254', + 'l5': 'windows-1254', + 'latin5': 'windows-1254', + 'windows-1254': 'windows-1254', + 'x-cp1254': 'windows-1254', + 'cp1255': 'windows-1255', + 'windows-1255': 'windows-1255', + 'x-cp1255': 'windows-1255', + 'cp1256': 'windows-1256', + 'windows-1256': 'windows-1256', + 'x-cp1256': 'windows-1256', + 'cp1257': 'windows-1257', + 'windows-1257': 'windows-1257', + 'x-cp1257': 'windows-1257', + 'cp1258': 'windows-1258', + 'windows-1258': 'windows-1258', + 'x-cp1258': 'windows-1258', + 'x-mac-cyrillic': 'x-mac-cyrillic', + 'x-mac-ukrainian': 'x-mac-cyrillic', + 'chinese': 'gbk', + 'csgb2312': 'gbk', + 'csiso58gb231280': 'gbk', + 'gb2312': 'gbk', + 'gb_2312': 'gbk', + 'gb_2312-80': 'gbk', + 'gbk': 'gbk', + 'iso-ir-58': 'gbk', + 'x-gbk': 'gbk', + 'gb18030': 'gb18030', + 'hz-gb-2312': 'hz-gb-2312', + 'big5': 'big5', + 'big5-hkscs': 'big5', + 'cn-big5': 'big5', + 'csbig5': 'big5', + 'x-x-big5': 'big5', + 'cseucpkdfmtjapanese': 'euc-jp', + 'euc-jp': 'euc-jp', + 'x-euc-jp': 'euc-jp', + 'csiso2022jp': 'iso-2022-jp', + 'iso-2022-jp': 'iso-2022-jp', + 'csshiftjis': 'shift_jis', + 'ms_kanji': 'shift_jis', + 'shift-jis': 'shift_jis', + 'shift_jis': 'shift_jis', + 'sjis': 'shift_jis', + 'windows-31j': 'shift_jis', + 'x-sjis': 'shift_jis', + 'cseuckr': 'euc-kr', + 'csksc56011987': 'euc-kr', + 'euc-kr': 'euc-kr', + 'iso-ir-149': 'euc-kr', + 'korean': 'euc-kr', + 'ks_c_5601-1987': 'euc-kr', + 'ks_c_5601-1989': 'euc-kr', + 'ksc5601': 'euc-kr', + 'ksc_5601': 'euc-kr', + 'windows-949': 'euc-kr', + 'csiso2022kr': 'iso-2022-kr', + 'iso-2022-kr': 'iso-2022-kr', + 'utf-16be': 'utf-16be', + 'utf-16': 'utf-16le', + 'utf-16le': 'utf-16le', + 'x-user-defined': 'x-user-defined', +} diff --git a/robot/lib/python3.8/site-packages/webencodings/mklabels.py b/robot/lib/python3.8/site-packages/webencodings/mklabels.py new file mode 100644 index 0000000000000000000000000000000000000000..295dc928ba71fc00caa52708ac70097abe6dc3e4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/webencodings/mklabels.py @@ -0,0 +1,59 @@ +""" + + webencodings.mklabels + ~~~~~~~~~~~~~~~~~~~~~ + + Regenarate the webencodings.labels module. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +import json +try: + from urllib import urlopen +except ImportError: + from urllib.request import urlopen + + +def assert_lower(string): + assert string == string.lower() + return string + + +def generate(url): + parts = ['''\ +""" + + webencodings.labels + ~~~~~~~~~~~~~~~~~~~ + + Map encoding labels to their name. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +# XXX Do not edit! +# This file is automatically generated by mklabels.py + +LABELS = { +'''] + labels = [ + (repr(assert_lower(label)).lstrip('u'), + repr(encoding['name']).lstrip('u')) + for category in json.loads(urlopen(url).read().decode('ascii')) + for encoding in category['encodings'] + for label in encoding['labels']] + max_len = max(len(label) for label, name in labels) + parts.extend( + ' %s:%s %s,\n' % (label, ' ' * (max_len - len(label)), name) + for label, name in labels) + parts.append('}') + return ''.join(parts) + + +if __name__ == '__main__': + print(generate('http://encoding.spec.whatwg.org/encodings.json')) diff --git a/robot/lib/python3.8/site-packages/webencodings/tests.py b/robot/lib/python3.8/site-packages/webencodings/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..e12c10d033026f09cf97b81d29555e12aae8c762 --- /dev/null +++ b/robot/lib/python3.8/site-packages/webencodings/tests.py @@ -0,0 +1,153 @@ +# coding: utf-8 +""" + + webencodings.tests + ~~~~~~~~~~~~~~~~~~ + + A basic test suite for Encoding. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +from __future__ import unicode_literals + +from . import (lookup, LABELS, decode, encode, iter_decode, iter_encode, + IncrementalDecoder, IncrementalEncoder, UTF8) + + +def assert_raises(exception, function, *args, **kwargs): + try: + function(*args, **kwargs) + except exception: + return + else: # pragma: no cover + raise AssertionError('Did not raise %s.' % exception) + + +def test_labels(): + assert lookup('utf-8').name == 'utf-8' + assert lookup('Utf-8').name == 'utf-8' + assert lookup('UTF-8').name == 'utf-8' + assert lookup('utf8').name == 'utf-8' + assert lookup('utf8').name == 'utf-8' + assert lookup('utf8 ').name == 'utf-8' + assert lookup(' \r\nutf8\t').name == 'utf-8' + assert lookup('u8') is None # Python label. + assert lookup('utf-8 ') is None # Non-ASCII white space. + + assert lookup('US-ASCII').name == 'windows-1252' + assert lookup('iso-8859-1').name == 'windows-1252' + assert lookup('latin1').name == 'windows-1252' + assert lookup('LATIN1').name == 'windows-1252' + assert lookup('latin-1') is None + assert lookup('LATİN1') is None # ASCII-only case insensitivity. + + +def test_all_labels(): + for label in LABELS: + assert decode(b'', label) == ('', lookup(label)) + assert encode('', label) == b'' + for repeat in [0, 1, 12]: + output, _ = iter_decode([b''] * repeat, label) + assert list(output) == [] + assert list(iter_encode([''] * repeat, label)) == [] + decoder = IncrementalDecoder(label) + assert decoder.decode(b'') == '' + assert decoder.decode(b'', final=True) == '' + encoder = IncrementalEncoder(label) + assert encoder.encode('') == b'' + assert encoder.encode('', final=True) == b'' + # All encoding names are valid labels too: + for name in set(LABELS.values()): + assert lookup(name).name == name + + +def test_invalid_label(): + assert_raises(LookupError, decode, b'\xEF\xBB\xBF\xc3\xa9', 'invalid') + assert_raises(LookupError, encode, 'é', 'invalid') + assert_raises(LookupError, iter_decode, [], 'invalid') + assert_raises(LookupError, iter_encode, [], 'invalid') + assert_raises(LookupError, IncrementalDecoder, 'invalid') + assert_raises(LookupError, IncrementalEncoder, 'invalid') + + +def test_decode(): + assert decode(b'\x80', 'latin1') == ('€', lookup('latin1')) + assert decode(b'\x80', lookup('latin1')) == ('€', lookup('latin1')) + assert decode(b'\xc3\xa9', 'utf8') == ('é', lookup('utf8')) + assert decode(b'\xc3\xa9', UTF8) == ('é', lookup('utf8')) + assert decode(b'\xc3\xa9', 'ascii') == ('é', lookup('ascii')) + assert decode(b'\xEF\xBB\xBF\xc3\xa9', 'ascii') == ('é', lookup('utf8')) # UTF-8 with BOM + + assert decode(b'\xFE\xFF\x00\xe9', 'ascii') == ('é', lookup('utf-16be')) # UTF-16-BE with BOM + assert decode(b'\xFF\xFE\xe9\x00', 'ascii') == ('é', lookup('utf-16le')) # UTF-16-LE with BOM + assert decode(b'\xFE\xFF\xe9\x00', 'ascii') == ('\ue900', lookup('utf-16be')) + assert decode(b'\xFF\xFE\x00\xe9', 'ascii') == ('\ue900', lookup('utf-16le')) + + assert decode(b'\x00\xe9', 'UTF-16BE') == ('é', lookup('utf-16be')) + assert decode(b'\xe9\x00', 'UTF-16LE') == ('é', lookup('utf-16le')) + assert decode(b'\xe9\x00', 'UTF-16') == ('é', lookup('utf-16le')) + + assert decode(b'\xe9\x00', 'UTF-16BE') == ('\ue900', lookup('utf-16be')) + assert decode(b'\x00\xe9', 'UTF-16LE') == ('\ue900', lookup('utf-16le')) + assert decode(b'\x00\xe9', 'UTF-16') == ('\ue900', lookup('utf-16le')) + + +def test_encode(): + assert encode('é', 'latin1') == b'\xe9' + assert encode('é', 'utf8') == b'\xc3\xa9' + assert encode('é', 'utf8') == b'\xc3\xa9' + assert encode('é', 'utf-16') == b'\xe9\x00' + assert encode('é', 'utf-16le') == b'\xe9\x00' + assert encode('é', 'utf-16be') == b'\x00\xe9' + + +def test_iter_decode(): + def iter_decode_to_string(input, fallback_encoding): + output, _encoding = iter_decode(input, fallback_encoding) + return ''.join(output) + assert iter_decode_to_string([], 'latin1') == '' + assert iter_decode_to_string([b''], 'latin1') == '' + assert iter_decode_to_string([b'\xe9'], 'latin1') == 'é' + assert iter_decode_to_string([b'hello'], 'latin1') == 'hello' + assert iter_decode_to_string([b'he', b'llo'], 'latin1') == 'hello' + assert iter_decode_to_string([b'hell', b'o'], 'latin1') == 'hello' + assert iter_decode_to_string([b'\xc3\xa9'], 'latin1') == 'é' + assert iter_decode_to_string([b'\xEF\xBB\xBF\xc3\xa9'], 'latin1') == 'é' + assert iter_decode_to_string([ + b'\xEF\xBB\xBF', b'\xc3', b'\xa9'], 'latin1') == 'é' + assert iter_decode_to_string([ + b'\xEF\xBB\xBF', b'a', b'\xc3'], 'latin1') == 'a\uFFFD' + assert iter_decode_to_string([ + b'', b'\xEF', b'', b'', b'\xBB\xBF\xc3', b'\xa9'], 'latin1') == 'é' + assert iter_decode_to_string([b'\xEF\xBB\xBF'], 'latin1') == '' + assert iter_decode_to_string([b'\xEF\xBB'], 'latin1') == 'ï»' + assert iter_decode_to_string([b'\xFE\xFF\x00\xe9'], 'latin1') == 'é' + assert iter_decode_to_string([b'\xFF\xFE\xe9\x00'], 'latin1') == 'é' + assert iter_decode_to_string([ + b'', b'\xFF', b'', b'', b'\xFE\xe9', b'\x00'], 'latin1') == 'é' + assert iter_decode_to_string([ + b'', b'h\xe9', b'llo'], 'x-user-defined') == 'h\uF7E9llo' + + +def test_iter_encode(): + assert b''.join(iter_encode([], 'latin1')) == b'' + assert b''.join(iter_encode([''], 'latin1')) == b'' + assert b''.join(iter_encode(['é'], 'latin1')) == b'\xe9' + assert b''.join(iter_encode(['', 'é', '', ''], 'latin1')) == b'\xe9' + assert b''.join(iter_encode(['', 'é', '', ''], 'utf-16')) == b'\xe9\x00' + assert b''.join(iter_encode(['', 'é', '', ''], 'utf-16le')) == b'\xe9\x00' + assert b''.join(iter_encode(['', 'é', '', ''], 'utf-16be')) == b'\x00\xe9' + assert b''.join(iter_encode([ + '', 'h\uF7E9', '', 'llo'], 'x-user-defined')) == b'h\xe9llo' + + +def test_x_user_defined(): + encoded = b'2,\x0c\x0b\x1aO\xd9#\xcb\x0f\xc9\xbbt\xcf\xa8\xca' + decoded = '2,\x0c\x0b\x1aO\uf7d9#\uf7cb\x0f\uf7c9\uf7bbt\uf7cf\uf7a8\uf7ca' + encoded = b'aa' + decoded = 'aa' + assert decode(encoded, 'x-user-defined') == (decoded, lookup('x-user-defined')) + assert encode(decoded, 'x-user-defined') == encoded diff --git a/robot/lib/python3.8/site-packages/webencodings/x_user_defined.py b/robot/lib/python3.8/site-packages/webencodings/x_user_defined.py new file mode 100644 index 0000000000000000000000000000000000000000..d16e326024c05a59548619e13258acad781e0a6d --- /dev/null +++ b/robot/lib/python3.8/site-packages/webencodings/x_user_defined.py @@ -0,0 +1,325 @@ +# coding: utf-8 +""" + + webencodings.x_user_defined + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + An implementation of the x-user-defined encoding. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +from __future__ import unicode_literals + +import codecs + + +### Codec APIs + +class Codec(codecs.Codec): + + def encode(self, input, errors='strict'): + return codecs.charmap_encode(input, errors, encoding_table) + + def decode(self, input, errors='strict'): + return codecs.charmap_decode(input, errors, decoding_table) + + +class IncrementalEncoder(codecs.IncrementalEncoder): + def encode(self, input, final=False): + return codecs.charmap_encode(input, self.errors, encoding_table)[0] + + +class IncrementalDecoder(codecs.IncrementalDecoder): + def decode(self, input, final=False): + return codecs.charmap_decode(input, self.errors, decoding_table)[0] + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + + +class StreamReader(Codec, codecs.StreamReader): + pass + + +### encodings module API + +codec_info = codecs.CodecInfo( + name='x-user-defined', + encode=Codec().encode, + decode=Codec().decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamreader=StreamReader, + streamwriter=StreamWriter, +) + + +### Decoding Table + +# Python 3: +# for c in range(256): print(' %r' % chr(c if c < 128 else c + 0xF700)) +decoding_table = ( + '\x00' + '\x01' + '\x02' + '\x03' + '\x04' + '\x05' + '\x06' + '\x07' + '\x08' + '\t' + '\n' + '\x0b' + '\x0c' + '\r' + '\x0e' + '\x0f' + '\x10' + '\x11' + '\x12' + '\x13' + '\x14' + '\x15' + '\x16' + '\x17' + '\x18' + '\x19' + '\x1a' + '\x1b' + '\x1c' + '\x1d' + '\x1e' + '\x1f' + ' ' + '!' + '"' + '#' + '$' + '%' + '&' + "'" + '(' + ')' + '*' + '+' + ',' + '-' + '.' + '/' + '0' + '1' + '2' + '3' + '4' + '5' + '6' + '7' + '8' + '9' + ':' + ';' + '<' + '=' + '>' + '?' + '@' + 'A' + 'B' + 'C' + 'D' + 'E' + 'F' + 'G' + 'H' + 'I' + 'J' + 'K' + 'L' + 'M' + 'N' + 'O' + 'P' + 'Q' + 'R' + 'S' + 'T' + 'U' + 'V' + 'W' + 'X' + 'Y' + 'Z' + '[' + '\\' + ']' + '^' + '_' + '`' + 'a' + 'b' + 'c' + 'd' + 'e' + 'f' + 'g' + 'h' + 'i' + 'j' + 'k' + 'l' + 'm' + 'n' + 'o' + 'p' + 'q' + 'r' + 's' + 't' + 'u' + 'v' + 'w' + 'x' + 'y' + 'z' + '{' + '|' + '}' + '~' + '\x7f' + '\uf780' + '\uf781' + '\uf782' + '\uf783' + '\uf784' + '\uf785' + '\uf786' + '\uf787' + '\uf788' + '\uf789' + '\uf78a' + '\uf78b' + '\uf78c' + '\uf78d' + '\uf78e' + '\uf78f' + '\uf790' + '\uf791' + '\uf792' + '\uf793' + '\uf794' + '\uf795' + '\uf796' + '\uf797' + '\uf798' + '\uf799' + '\uf79a' + '\uf79b' + '\uf79c' + '\uf79d' + '\uf79e' + '\uf79f' + '\uf7a0' + '\uf7a1' + '\uf7a2' + '\uf7a3' + '\uf7a4' + '\uf7a5' + '\uf7a6' + '\uf7a7' + '\uf7a8' + '\uf7a9' + '\uf7aa' + '\uf7ab' + '\uf7ac' + '\uf7ad' + '\uf7ae' + '\uf7af' + '\uf7b0' + '\uf7b1' + '\uf7b2' + '\uf7b3' + '\uf7b4' + '\uf7b5' + '\uf7b6' + '\uf7b7' + '\uf7b8' + '\uf7b9' + '\uf7ba' + '\uf7bb' + '\uf7bc' + '\uf7bd' + '\uf7be' + '\uf7bf' + '\uf7c0' + '\uf7c1' + '\uf7c2' + '\uf7c3' + '\uf7c4' + '\uf7c5' + '\uf7c6' + '\uf7c7' + '\uf7c8' + '\uf7c9' + '\uf7ca' + '\uf7cb' + '\uf7cc' + '\uf7cd' + '\uf7ce' + '\uf7cf' + '\uf7d0' + '\uf7d1' + '\uf7d2' + '\uf7d3' + '\uf7d4' + '\uf7d5' + '\uf7d6' + '\uf7d7' + '\uf7d8' + '\uf7d9' + '\uf7da' + '\uf7db' + '\uf7dc' + '\uf7dd' + '\uf7de' + '\uf7df' + '\uf7e0' + '\uf7e1' + '\uf7e2' + '\uf7e3' + '\uf7e4' + '\uf7e5' + '\uf7e6' + '\uf7e7' + '\uf7e8' + '\uf7e9' + '\uf7ea' + '\uf7eb' + '\uf7ec' + '\uf7ed' + '\uf7ee' + '\uf7ef' + '\uf7f0' + '\uf7f1' + '\uf7f2' + '\uf7f3' + '\uf7f4' + '\uf7f5' + '\uf7f6' + '\uf7f7' + '\uf7f8' + '\uf7f9' + '\uf7fa' + '\uf7fb' + '\uf7fc' + '\uf7fd' + '\uf7fe' + '\uf7ff' +) + +### Encoding table +encoding_table = codecs.charmap_build(decoding_table) diff --git a/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/AUTHORS.txt b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/AUTHORS.txt new file mode 100644 index 0000000000000000000000000000000000000000..72c87d7d38ae7bf859717c333a5ee8230f6ce624 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/AUTHORS.txt @@ -0,0 +1,562 @@ +A_Rog +Aakanksha Agrawal <11389424+rasponic@users.noreply.github.com> +Abhinav Sagar <40603139+abhinavsagar@users.noreply.github.com> +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Tse +Adam Wentz +admin +Adrien Morison +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Aleks Bunin +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Alexey Popravka +Alli +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrey Bulgakov +Andrés Delfino <34587441+andresdelfino@users.noreply.github.com> +Andrés Delfino +Andy Freeland +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anuj Godase +AQNOUCH Mohammed +AraHaan +Arindam Choudhury +Armin Ronacher +Artem +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avner Cohen +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Darnell +Ben Hoyt +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Brandon L. Reiss +Brandt Bucher +Brett Randall +Brian Cristante <33549821+brcrista@users.noreply.github.com> +Brian Cristante +Brian Rosner +BrownTruck +Bruno Oliveira +Bruno Renié +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +c22 +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Chih-Hsuan Yen +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris McDonough +Chris Wolfe +Christian Heimes +Christian Oudard +Christopher Hunt +Christopher Snyder +Clark Boylan +Clay McClure +Cody +Cody Soyland +Colin Watson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Curtis Doty +cytolentino +Damian Quiroga +Dan Black +Dan Savilonis +Dan Sully +daniel +Daniel Collins +Daniel Hahler +Daniel Holth +Daniel Jost +Daniel Shaulov +Daniele Esposti +Daniele Procida +Danny Hermes +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Bordeynik +David Caro +David Evans +David Linke +David Pursehouse +David Tucker +David Wales +Davidovich +derwolfe +Desetude +Diego Caraballo +DiegoCaraballo +Dmitry Gladkov +Domen Kožar +Donald Stufft +Dongweiming +Douglas Thor +DrFeathers +Dustin Ingram +Dwayne Bailey +Ed Morley <501702+edmorley@users.noreply.github.com> +Ed Morley +Eitan Adler +ekristina +elainechan +Eli Schwartz +Eli Schwartz +Emil Burzo +Emil Styrke +Endoh Takanao +enoch +Erdinc Mutlu +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Ernest W Durbin III +Ernest W. Durbin III +Erwin Janssen +Eugene Vereshchagin +everdimension +Felix Yan +fiber-space +Filip Kokosiński +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gdanielson +Geoffrey Lehée +Geoffrey Sneddon +George Song +Georgi Valkov +Giftlin Rajaiah +gizmoguy1 +gkdoc <40815324+gkdoc@users.noreply.github.com> +Gopinath M <31352222+mgopi1990@users.noreply.github.com> +GOTO Hayato <3532528+gh640@users.noreply.github.com> +gpiks +Guilherme Espada +Guy Rozendorn +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +Herbert Pfennig +Hsiaoming Yang +Hugo +Hugo Lopes Tavares +Hugo van Kemenade +hugovk +Hynek Schlawack +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ilya Baryshev +INADA Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Ivan Pozdeev +Jacob Kim +jakirkham +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James Cleveland +James Cleveland +James Firth +James Polley +Jan Pokorný +Jannis Leidel +jarondl +Jason R. Coombs +Jay Graves +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jelmer Vernooij +jenix21 +Jeremy Stanley +Jeremy Zafran +Jiashuo Li +Jim Garrison +Jivan Amara +John Paton +John-Scott Atlakson +johnthagen +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joost Molenaar +Jorge Niedbalski +Joseph Long +Josh Bronson +Josh Hansen +Josh Schneier +Juanjo Bazán +Julian Berman +Julian Gethmann +Julien Demoor +jwg4 +Jyrki Pulliainen +Kai Chen +Kamal Bin Mustafa +kaustav haldar +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kexuan Sun +Kit Randel +kpinc +Krishna Oza +Kumar McMillan +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurie Opperman +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +Loren Carvalho +Lucas Cimon +Ludovic Gasc +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +László Kiss Kollár +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark Williams +Mark Williams +Markus Hametner +Masaki +Masklinn +Matej Stuchlik +Mathew Jennings +Mathieu Bridon +Matt Good +Matt Maker +Matt Robenolt +matthew +Matthew Einhorn +Matthew Gilliard +Matthew Iversen +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna <44498973+mbaluna@users.noreply.github.com> +mdebi <17590103+mdebi@users.noreply.github.com> +memoselyk +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Williamson +michaelpacer +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Monica Baluna +montefra +Monty Taylor +Nate Coraor +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nikhil Benesch +Nitesh Sharma +Nowell Strite +NtaleGrey +nvdv +Ofekmeister +ofrinevo +Oliver Jeeves +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +OMOTO Kenji +Omry Yadan +Oren Held +Oscar Benjamin +Oz N Tiram +Pachwenko <32424503+Pachwenko@users.noreply.github.com> +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavithra Eswaramoorthy <33131404+QueenCoffee@users.noreply.github.com> +Pawel Jasinski +Pekka Klärck +Peter Lisák +Peter Waller +petr-tik +Phaneendra Chiruvella +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Pratik Mallya +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal <7895pulkit@gmail.com> +Qiangning Hong +Quentin Pradet +R. David Murray +Rafael Caricio +Ralf Schmitt +Razzi Abuissa +rdb +Remi Rampin +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Richard Jones +RobberPhex +Robert Collins +Robert McGibbon +Robert T. McGibbon +robin elisha robinson +Roey Berman +Rohan Jain +Rohan Jain +Rohan Jain +Roman Bogorodskiy +Romuald Brunet +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +Roy Wellington Ⅳ +Ryan Wooden +ryneeverett +Sachi King +Salvatore Rinchiera +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sergey Vasilyev +Seth Woodworth +Shlomi Fish +Shovan Maity +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +Sorin Sbarnea +Stavros Korokithakis +Stefan Scherfke +Stephan Erb +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +stonebig +Stéphane Bidoul (ACSONE) +Stéphane Bidoul +Stéphane Klein +Sumana Harihareswara +Sviatoslav Sydorenko +Sviatoslav Sydorenko +Swat009 +Takayuki SHIMIZUKAWA +tbeswick +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tom Forbes +Tom Freudenheim +Tom V +Tomas Orsava +Tomer Chachamu +Tony Beswick +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +Travis Swicegood +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Philippon +Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> +Vitaly Babiy +Vladimir Rutsky +W. Trevor King +Wil Tan +Wilfred Hughes +William ML Leslie +William T Olson +Wilson Mo +wim glenn +Wolfgang Maier +Xavier Fernandez +Xavier Fernandez +xoviat +xtreak +YAMAMOTO Takashi +Yen Chi Hsuan +Yeray Diaz Diaz +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Zearin +Zearin +Zhiping Deng +Zvezdan Petkovic +Łukasz Langa +Семён Марьясин diff --git a/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/INSTALLER b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/LICENSE.txt b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..737fec5c5352af3d9a6a47a0670da4bdb52c5725 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2019 The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/METADATA b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..49c88b125d1ef2989b233e6250cf58d93f3051fa --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/METADATA @@ -0,0 +1,66 @@ +Metadata-Version: 2.1 +Name: wheel +Version: 0.34.2 +Summary: A built-package format for Python +Home-page: https://github.com/pypa/wheel +Author: Daniel Holth +Author-email: dholth@fastmail.fm +Maintainer: Alex Grönholm +Maintainer-email: alex.gronholm@nextday.fi +License: MIT +Project-URL: Documentation, https://wheel.readthedocs.io/ +Project-URL: Changelog, https://wheel.readthedocs.io/en/stable/news.html +Project-URL: Issue Tracker, https://github.com/pypa/wheel/issues +Keywords: wheel,packaging +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Topic :: System :: Archiving :: Packaging +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7 +Provides-Extra: test +Requires-Dist: pytest-cov ; extra == 'test' +Requires-Dist: pytest (>=3.0.0) ; extra == 'test' + +wheel +===== + +This library is the reference implementation of the Python wheel packaging +standard, as defined in `PEP 427`_. + +It has two different roles: + +#. A setuptools_ extension for building wheels that provides the + ``bdist_wheel`` setuptools command +#. A command line tool for working with wheel files + +It should be noted that wheel is **not** intended to be used as a library, and +as such there is no stable, public API. + +.. _PEP 427: https://www.python.org/dev/peps/pep-0427/ +.. _setuptools: https://pypi.org/project/setuptools/ + +Documentation +------------- + +The documentation_ can be found on Read The Docs. + +.. _documentation: https://wheel.readthedocs.io/ + +Code of Conduct +--------------- + +Everyone interacting in the wheel project's codebases, issue trackers, chat +rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_. + +.. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/ + + diff --git a/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/RECORD b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..489625eb9f22d575096be0ce6c1520c608a533d7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/RECORD @@ -0,0 +1,43 @@ +wheel/__init__.py,sha256=HnvQS9U0JqVi8rcO-9DbcHRmVtnblu7VwdzoKbMiZDQ,23 +wheel/__main__.py,sha256=lF-YLO4hdQmoWuh4eWZd8YL1U95RSdm76sNLBXa0vjE,417 +wheel/_version.py,sha256=KiDEywHVTrXNxe6ojGe-7gEm6gUYge5r_fThX1den7Y,133 +wheel/bdist_wheel.py,sha256=fgVFnJ2cC3MkU5Wy0nNLCzbJoGorSzSrzM9PFq4HWj0,15840 +wheel/macosx_libfile.py,sha256=NhNz1C3zF_78iMUd4ij0le1jVJNnz93tCAICSKXgYvg,11858 +wheel/metadata.py,sha256=siS-hs_DTT0ScpbzloYbQSGYXUDC_Tim10ixcYWSM-4,4517 +wheel/pep425tags.py,sha256=tuXvpyhYJHDcsXHgSRonny1X3kG3TSn7CN9nL9tZUMA,9071 +wheel/pkginfo.py,sha256=GR76kupQzn1x9sKDaXuE6B6FsZ4OkfRtG7pndlXPvQ4,1257 +wheel/util.py,sha256=mnNZkJCi9DHLI_q4lTudoD0mW97h_AoAWl7prNPLXJc,938 +wheel/wheelfile.py,sha256=WsWfD-OBgHxxeF7SxRi3OX6l_qxtP06b2ZK0NVhT_n0,7298 +wheel/cli/__init__.py,sha256=GWSoGUpRabTf8bk3FsNTPrc5Fsr8YOv2dX55iY2W7eY,2572 +wheel/cli/convert.py,sha256=7F4vj23A2OghDDWn9gX2V-_TeXMza1a5nIejmFGEUJM,9498 +wheel/cli/pack.py,sha256=S-J1iIy1GPDTTDdn-_SwxGa7N729h4iZNI11EDFCqfA,3208 +wheel/cli/unpack.py,sha256=0VWzT7U_xyenTPwEVavxqvdee93GPvAFHnR3Uu91aRc,673 +wheel-0.34.2.dist-info/AUTHORS.txt,sha256=RtqU9KfonVGhI48DAA4-yTOBUhBtQTjFhaDzHoyh7uU,21518 +wheel-0.34.2.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090 +wheel-0.34.2.dist-info/METADATA,sha256=Zh9PzJB14h0gUthWq4ghxft8PJiybKKBjvyikMapY_k,2214 +wheel-0.34.2.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +wheel-0.34.2.dist-info/entry_points.txt,sha256=N8HbYFST3yrNQYeB2wXWBEPUhFsEtKNRPaCFGJPyqyc,108 +wheel-0.34.2.dist-info/top_level.txt,sha256=HxSBIbgEstMPe4eFawhA66Mq-QYHMopXVoAncfjb_1c,6 +wheel-0.34.2.dist-info/RECORD,, +../../../bin/wheel-3.8,, +wheel/cli/convert.cpython-38.pyc,, +wheel/util.cpython-38.pyc,, +wheel/cli/__init__.cpython-38.pyc,, +wheel/_version.cpython-38.pyc,, +wheel/__pycache__,, +wheel/pkginfo.cpython-38.pyc,, +wheel/cli/pack.cpython-38.pyc,, +wheel/cli/__pycache__,, +../../../bin/wheel,, +wheel/wheelfile.cpython-38.pyc,, +wheel/bdist_wheel.cpython-38.pyc,, +wheel/metadata.cpython-38.pyc,, +wheel/cli/unpack.cpython-38.pyc,, +wheel-0.34.2.dist-info/__pycache__,, +wheel-0.34.2.virtualenv,, +../../../bin/wheel3,, +wheel/pep425tags.cpython-38.pyc,, +wheel-0.34.2.dist-info/INSTALLER,, +wheel/__init__.cpython-38.pyc,, +wheel/macosx_libfile.cpython-38.pyc,, +wheel/__main__.cpython-38.pyc,, \ No newline at end of file diff --git a/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/WHEEL b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ef99c6cf3283b50a273ac4c6d009a0aa85597070 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/entry_points.txt b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..b27acaddfde8258ee5b9eb70963ff108041bd998 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/entry_points.txt @@ -0,0 +1,6 @@ +[console_scripts] +wheel = wheel.cli:main + +[distutils.commands] +bdist_wheel = wheel.bdist_wheel:bdist_wheel + diff --git a/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/top_level.txt b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..2309722a93d261ba6840e2a1d18a96ac5b71f7b8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel-0.34.2.dist-info/top_level.txt @@ -0,0 +1 @@ +wheel diff --git a/robot/lib/python3.8/site-packages/wheel-0.34.2.virtualenv b/robot/lib/python3.8/site-packages/wheel-0.34.2.virtualenv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/robot/lib/python3.8/site-packages/wheel/__init__.py b/robot/lib/python3.8/site-packages/wheel/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3c92ca223a422413939fe64a1f1a460021b69c64 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/__init__.py @@ -0,0 +1 @@ +__version__ = '0.34.2' diff --git a/robot/lib/python3.8/site-packages/wheel/__main__.py b/robot/lib/python3.8/site-packages/wheel/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..b3773a20e08b985d4160b624fbfe1742f35472cd --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/__main__.py @@ -0,0 +1,19 @@ +""" +Wheel command line tool (enable python -m wheel syntax) +""" + +import sys + + +def main(): # needed for console script + if __package__ == '': + # To be able to run 'python wheel-0.9.whl/wheel': + import os.path + path = os.path.dirname(os.path.dirname(__file__)) + sys.path[0:0] = [path] + import wheel.cli + sys.exit(wheel.cli.main()) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/robot/lib/python3.8/site-packages/wheel/_version.py b/robot/lib/python3.8/site-packages/wheel/_version.py new file mode 100644 index 0000000000000000000000000000000000000000..5f581f72ae171130492a53981edab2ad0e872ad8 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/_version.py @@ -0,0 +1,4 @@ +# coding: utf-8 +# file generated by setuptools_scm +# don't change, don't track in version control +version = '0.33.6.post32+gd3d7a43' diff --git a/robot/lib/python3.8/site-packages/wheel/bdist_wheel.py b/robot/lib/python3.8/site-packages/wheel/bdist_wheel.py new file mode 100644 index 0000000000000000000000000000000000000000..5cece1f2a25df012fe3beccea0c27d043b5e0f0c --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/bdist_wheel.py @@ -0,0 +1,403 @@ +""" +Create a wheel (.whl) distribution. + +A wheel is a built archive format. +""" + +import os +import shutil +import stat +import sys +import re +from collections import OrderedDict +from email.generator import Generator +from distutils.core import Command +from distutils.sysconfig import get_python_version +from distutils import log as logger +from glob import iglob +from shutil import rmtree +from warnings import warn +from zipfile import ZIP_DEFLATED, ZIP_STORED + +import pkg_resources + +from .pep425tags import get_abbr_impl, get_impl_ver, get_abi_tag, get_platform +from .pkginfo import write_pkg_info +from .metadata import pkginfo_to_metadata +from .wheelfile import WheelFile +from . import pep425tags +from . import __version__ as wheel_version + + +safe_name = pkg_resources.safe_name +safe_version = pkg_resources.safe_version + +PY_LIMITED_API_PATTERN = r'cp3\d' + + +def safer_name(name): + return safe_name(name).replace('-', '_') + + +def safer_version(version): + return safe_version(version).replace('-', '_') + + +def remove_readonly(func, path, excinfo): + print(str(excinfo[1])) + os.chmod(path, stat.S_IWRITE) + func(path) + + +class bdist_wheel(Command): + + description = 'create a wheel distribution' + + supported_compressions = OrderedDict([ + ('stored', ZIP_STORED), + ('deflated', ZIP_DEFLATED) + ]) + + user_options = [('bdist-dir=', 'b', + "temporary directory for creating the distribution"), + ('plat-name=', 'p', + "platform name to embed in generated filenames " + "(default: %s)" % get_platform(None)), + ('keep-temp', 'k', + "keep the pseudo-installation tree around after " + + "creating the distribution archive"), + ('dist-dir=', 'd', + "directory to put final built distributions in"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ('relative', None, + "build the archive using relative paths " + "(default: false)"), + ('owner=', 'u', + "Owner name used when creating a tar file" + " [default: current user]"), + ('group=', 'g', + "Group name used when creating a tar file" + " [default: current group]"), + ('universal', None, + "make a universal wheel" + " (default: false)"), + ('compression=', None, + "zipfile compression (one of: {})" + " (default: 'deflated')" + .format(', '.join(supported_compressions))), + ('python-tag=', None, + "Python implementation compatibility tag" + " (default: py%s)" % get_impl_ver()[0]), + ('build-number=', None, + "Build number for this particular version. " + "As specified in PEP-0427, this must start with a digit. " + "[default: None]"), + ('py-limited-api=', None, + "Python tag (cp32|cp33|cpNN) for abi3 wheel tag" + " (default: false)"), + ] + + boolean_options = ['keep-temp', 'skip-build', 'relative', 'universal'] + + def initialize_options(self): + self.bdist_dir = None + self.data_dir = None + self.plat_name = None + self.plat_tag = None + self.format = 'zip' + self.keep_temp = False + self.dist_dir = None + self.egginfo_dir = None + self.root_is_pure = None + self.skip_build = None + self.relative = False + self.owner = None + self.group = None + self.universal = False + self.compression = 'deflated' + self.python_tag = 'py' + get_impl_ver()[0] + self.build_number = None + self.py_limited_api = False + self.plat_name_supplied = False + + def finalize_options(self): + if self.bdist_dir is None: + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'wheel') + + self.data_dir = self.wheel_dist_name + '.data' + self.plat_name_supplied = self.plat_name is not None + + try: + self.compression = self.supported_compressions[self.compression] + except KeyError: + raise ValueError('Unsupported compression: {}'.format(self.compression)) + + need_options = ('dist_dir', 'plat_name', 'skip_build') + + self.set_undefined_options('bdist', + *zip(need_options, need_options)) + + self.root_is_pure = not (self.distribution.has_ext_modules() + or self.distribution.has_c_libraries()) + + if self.py_limited_api and not re.match(PY_LIMITED_API_PATTERN, self.py_limited_api): + raise ValueError("py-limited-api must match '%s'" % PY_LIMITED_API_PATTERN) + + # Support legacy [wheel] section for setting universal + wheel = self.distribution.get_option_dict('wheel') + if 'universal' in wheel: + # please don't define this in your global configs + logger.warn('The [wheel] section is deprecated. Use [bdist_wheel] instead.') + val = wheel['universal'][1].strip() + if val.lower() in ('1', 'true', 'yes'): + self.universal = True + + if self.build_number is not None and not self.build_number[:1].isdigit(): + raise ValueError("Build tag (build-number) must start with a digit.") + + @property + def wheel_dist_name(self): + """Return distribution full name with - replaced with _""" + components = (safer_name(self.distribution.get_name()), + safer_version(self.distribution.get_version())) + if self.build_number: + components += (self.build_number,) + return '-'.join(components) + + def get_tag(self): + # bdist sets self.plat_name if unset, we should only use it for purepy + # wheels if the user supplied it. + if self.plat_name_supplied: + plat_name = self.plat_name + elif self.root_is_pure: + plat_name = 'any' + else: + # macosx contains system version in platform name so need special handle + if self.plat_name and not self.plat_name.startswith("macosx"): + plat_name = self.plat_name + else: + plat_name = get_platform(self.bdist_dir) + + if plat_name in ('linux-x86_64', 'linux_x86_64') and sys.maxsize == 2147483647: + plat_name = 'linux_i686' + + plat_name = plat_name.replace('-', '_').replace('.', '_') + + if self.root_is_pure: + if self.universal: + impl = 'py2.py3' + else: + impl = self.python_tag + tag = (impl, 'none', plat_name) + else: + impl_name = get_abbr_impl() + impl_ver = get_impl_ver() + impl = impl_name + impl_ver + # We don't work on CPython 3.1, 3.0. + if self.py_limited_api and (impl_name + impl_ver).startswith('cp3'): + impl = self.py_limited_api + abi_tag = 'abi3' + else: + abi_tag = str(get_abi_tag()).lower() + tag = (impl, abi_tag, plat_name) + supported_tags = pep425tags.get_supported( + self.bdist_dir, + supplied_platform=plat_name if self.plat_name_supplied else None) + # XXX switch to this alternate implementation for non-pure: + if not self.py_limited_api: + assert tag == supported_tags[0], "%s != %s" % (tag, supported_tags[0]) + assert tag in supported_tags, "would build wheel with unsupported tag {}".format(tag) + return tag + + def run(self): + build_scripts = self.reinitialize_command('build_scripts') + build_scripts.executable = 'python' + build_scripts.force = True + + build_ext = self.reinitialize_command('build_ext') + build_ext.inplace = False + + if not self.skip_build: + self.run_command('build') + + install = self.reinitialize_command('install', + reinit_subcommands=True) + install.root = self.bdist_dir + install.compile = False + install.skip_build = self.skip_build + install.warn_dir = False + + # A wheel without setuptools scripts is more cross-platform. + # Use the (undocumented) `no_ep` option to setuptools' + # install_scripts command to avoid creating entry point scripts. + install_scripts = self.reinitialize_command('install_scripts') + install_scripts.no_ep = True + + # Use a custom scheme for the archive, because we have to decide + # at installation time which scheme to use. + for key in ('headers', 'scripts', 'data', 'purelib', 'platlib'): + setattr(install, + 'install_' + key, + os.path.join(self.data_dir, key)) + + basedir_observed = '' + + if os.name == 'nt': + # win32 barfs if any of these are ''; could be '.'? + # (distutils.command.install:change_roots bug) + basedir_observed = os.path.normpath(os.path.join(self.data_dir, '..')) + self.install_libbase = self.install_lib = basedir_observed + + setattr(install, + 'install_purelib' if self.root_is_pure else 'install_platlib', + basedir_observed) + + logger.info("installing to %s", self.bdist_dir) + + self.run_command('install') + + impl_tag, abi_tag, plat_tag = self.get_tag() + archive_basename = "{}-{}-{}-{}".format(self.wheel_dist_name, impl_tag, abi_tag, plat_tag) + if not self.relative: + archive_root = self.bdist_dir + else: + archive_root = os.path.join( + self.bdist_dir, + self._ensure_relative(install.install_base)) + + self.set_undefined_options('install_egg_info', ('target', 'egginfo_dir')) + distinfo_dirname = '{}-{}.dist-info'.format( + safer_name(self.distribution.get_name()), + safer_version(self.distribution.get_version())) + distinfo_dir = os.path.join(self.bdist_dir, distinfo_dirname) + self.egg2dist(self.egginfo_dir, distinfo_dir) + + self.write_wheelfile(distinfo_dir) + + # Make the archive + if not os.path.exists(self.dist_dir): + os.makedirs(self.dist_dir) + + wheel_path = os.path.join(self.dist_dir, archive_basename + '.whl') + with WheelFile(wheel_path, 'w', self.compression) as wf: + wf.write_files(archive_root) + + # Add to 'Distribution.dist_files' so that the "upload" command works + getattr(self.distribution, 'dist_files', []).append( + ('bdist_wheel', get_python_version(), wheel_path)) + + if not self.keep_temp: + logger.info('removing %s', self.bdist_dir) + if not self.dry_run: + rmtree(self.bdist_dir, onerror=remove_readonly) + + def write_wheelfile(self, wheelfile_base, generator='bdist_wheel (' + wheel_version + ')'): + from email.message import Message + msg = Message() + msg['Wheel-Version'] = '1.0' # of the spec + msg['Generator'] = generator + msg['Root-Is-Purelib'] = str(self.root_is_pure).lower() + if self.build_number is not None: + msg['Build'] = self.build_number + + # Doesn't work for bdist_wininst + impl_tag, abi_tag, plat_tag = self.get_tag() + for impl in impl_tag.split('.'): + for abi in abi_tag.split('.'): + for plat in plat_tag.split('.'): + msg['Tag'] = '-'.join((impl, abi, plat)) + + wheelfile_path = os.path.join(wheelfile_base, 'WHEEL') + logger.info('creating %s', wheelfile_path) + with open(wheelfile_path, 'w') as f: + Generator(f, maxheaderlen=0).flatten(msg) + + def _ensure_relative(self, path): + # copied from dir_util, deleted + drive, path = os.path.splitdrive(path) + if path[0:1] == os.sep: + path = drive + path[1:] + return path + + @property + def license_paths(self): + metadata = self.distribution.get_option_dict('metadata') + files = set() + patterns = sorted({ + option for option in metadata.get('license_files', ('', ''))[1].split() + }) + + if 'license_file' in metadata: + warn('The "license_file" option is deprecated. Use "license_files" instead.', + DeprecationWarning) + files.add(metadata['license_file'][1]) + + if 'license_file' not in metadata and 'license_files' not in metadata: + patterns = ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*') + + for pattern in patterns: + for path in iglob(pattern): + if path not in files and os.path.isfile(path): + logger.info('adding license file "%s" (matched pattern "%s")', path, pattern) + files.add(path) + + return files + + def egg2dist(self, egginfo_path, distinfo_path): + """Convert an .egg-info directory into a .dist-info directory""" + def adios(p): + """Appropriately delete directory, file or link.""" + if os.path.exists(p) and not os.path.islink(p) and os.path.isdir(p): + shutil.rmtree(p) + elif os.path.exists(p): + os.unlink(p) + + adios(distinfo_path) + + if not os.path.exists(egginfo_path): + # There is no egg-info. This is probably because the egg-info + # file/directory is not named matching the distribution name used + # to name the archive file. Check for this case and report + # accordingly. + import glob + pat = os.path.join(os.path.dirname(egginfo_path), '*.egg-info') + possible = glob.glob(pat) + err = "Egg metadata expected at %s but not found" % (egginfo_path,) + if possible: + alt = os.path.basename(possible[0]) + err += " (%s found - possible misnamed archive file?)" % (alt,) + + raise ValueError(err) + + if os.path.isfile(egginfo_path): + # .egg-info is a single file + pkginfo_path = egginfo_path + pkg_info = pkginfo_to_metadata(egginfo_path, egginfo_path) + os.mkdir(distinfo_path) + else: + # .egg-info is a directory + pkginfo_path = os.path.join(egginfo_path, 'PKG-INFO') + pkg_info = pkginfo_to_metadata(egginfo_path, pkginfo_path) + + # ignore common egg metadata that is useless to wheel + shutil.copytree(egginfo_path, distinfo_path, + ignore=lambda x, y: {'PKG-INFO', 'requires.txt', 'SOURCES.txt', + 'not-zip-safe'} + ) + + # delete dependency_links if it is only whitespace + dependency_links_path = os.path.join(distinfo_path, 'dependency_links.txt') + with open(dependency_links_path, 'r') as dependency_links_file: + dependency_links = dependency_links_file.read().strip() + if not dependency_links: + adios(dependency_links_path) + + write_pkg_info(os.path.join(distinfo_path, 'METADATA'), pkg_info) + + for license_path in self.license_paths: + filename = os.path.basename(license_path) + shutil.copy(license_path, os.path.join(distinfo_path, filename)) + + adios(egginfo_path) diff --git a/robot/lib/python3.8/site-packages/wheel/cli/__init__.py b/robot/lib/python3.8/site-packages/wheel/cli/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..95740bfb650514f6b17005b4bc8220858ebdfdf4 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/cli/__init__.py @@ -0,0 +1,88 @@ +""" +Wheel command-line utility. +""" + +from __future__ import print_function + +import argparse +import os +import sys + + +def require_pkgresources(name): + try: + import pkg_resources # noqa: F401 + except ImportError: + raise RuntimeError("'{0}' needs pkg_resources (part of setuptools).".format(name)) + + +class WheelError(Exception): + pass + + +def unpack_f(args): + from .unpack import unpack + unpack(args.wheelfile, args.dest) + + +def pack_f(args): + from .pack import pack + pack(args.directory, args.dest_dir, args.build_number) + + +def convert_f(args): + from .convert import convert + convert(args.files, args.dest_dir, args.verbose) + + +def version_f(args): + from .. import __version__ + print("wheel %s" % __version__) + + +def parser(): + p = argparse.ArgumentParser() + s = p.add_subparsers(help="commands") + + unpack_parser = s.add_parser('unpack', help='Unpack wheel') + unpack_parser.add_argument('--dest', '-d', help='Destination directory', + default='.') + unpack_parser.add_argument('wheelfile', help='Wheel file') + unpack_parser.set_defaults(func=unpack_f) + + repack_parser = s.add_parser('pack', help='Repack wheel') + repack_parser.add_argument('directory', help='Root directory of the unpacked wheel') + repack_parser.add_argument('--dest-dir', '-d', default=os.path.curdir, + help="Directory to store the wheel (default %(default)s)") + repack_parser.add_argument('--build-number', help="Build tag to use in the wheel name") + repack_parser.set_defaults(func=pack_f) + + convert_parser = s.add_parser('convert', help='Convert egg or wininst to wheel') + convert_parser.add_argument('files', nargs='*', help='Files to convert') + convert_parser.add_argument('--dest-dir', '-d', default=os.path.curdir, + help="Directory to store wheels (default %(default)s)") + convert_parser.add_argument('--verbose', '-v', action='store_true') + convert_parser.set_defaults(func=convert_f) + + version_parser = s.add_parser('version', help='Print version and exit') + version_parser.set_defaults(func=version_f) + + help_parser = s.add_parser('help', help='Show this help') + help_parser.set_defaults(func=lambda args: p.print_help()) + + return p + + +def main(): + p = parser() + args = p.parse_args() + if not hasattr(args, 'func'): + p.print_help() + else: + try: + args.func(args) + return 0 + except WheelError as e: + print(e, file=sys.stderr) + + return 1 diff --git a/robot/lib/python3.8/site-packages/wheel/cli/convert.py b/robot/lib/python3.8/site-packages/wheel/cli/convert.py new file mode 100644 index 0000000000000000000000000000000000000000..154f1b1e2a5b84ade4d44c9a5226b7979d21958b --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/cli/convert.py @@ -0,0 +1,269 @@ +import os.path +import re +import shutil +import sys +import tempfile +import zipfile +from distutils import dist +from glob import iglob + +from ..bdist_wheel import bdist_wheel +from ..wheelfile import WheelFile +from . import WheelError, require_pkgresources + +egg_info_re = re.compile(r''' + (?P.+?)-(?P.+?) + (-(?Ppy\d\.\d+) + (-(?P.+?))? + )?.egg$''', re.VERBOSE) + + +class _bdist_wheel_tag(bdist_wheel): + # allow the client to override the default generated wheel tag + # The default bdist_wheel implementation uses python and abi tags + # of the running python process. This is not suitable for + # generating/repackaging prebuild binaries. + + full_tag_supplied = False + full_tag = None # None or a (pytag, soabitag, plattag) triple + + def get_tag(self): + if self.full_tag_supplied and self.full_tag is not None: + return self.full_tag + else: + return bdist_wheel.get_tag(self) + + +def egg2wheel(egg_path, dest_dir): + filename = os.path.basename(egg_path) + match = egg_info_re.match(filename) + if not match: + raise WheelError('Invalid egg file name: {}'.format(filename)) + + egg_info = match.groupdict() + dir = tempfile.mkdtemp(suffix="_e2w") + if os.path.isfile(egg_path): + # assume we have a bdist_egg otherwise + with zipfile.ZipFile(egg_path) as egg: + egg.extractall(dir) + else: + # support buildout-style installed eggs directories + for pth in os.listdir(egg_path): + src = os.path.join(egg_path, pth) + if os.path.isfile(src): + shutil.copy2(src, dir) + else: + shutil.copytree(src, os.path.join(dir, pth)) + + pyver = egg_info['pyver'] + if pyver: + pyver = egg_info['pyver'] = pyver.replace('.', '') + + arch = (egg_info['arch'] or 'any').replace('.', '_').replace('-', '_') + + # assume all binary eggs are for CPython + abi = 'cp' + pyver[2:] if arch != 'any' else 'none' + + root_is_purelib = egg_info['arch'] is None + if root_is_purelib: + bw = bdist_wheel(dist.Distribution()) + else: + bw = _bdist_wheel_tag(dist.Distribution()) + + bw.root_is_pure = root_is_purelib + bw.python_tag = pyver + bw.plat_name_supplied = True + bw.plat_name = egg_info['arch'] or 'any' + if not root_is_purelib: + bw.full_tag_supplied = True + bw.full_tag = (pyver, abi, arch) + + dist_info_dir = os.path.join(dir, '{name}-{ver}.dist-info'.format(**egg_info)) + bw.egg2dist(os.path.join(dir, 'EGG-INFO'), dist_info_dir) + bw.write_wheelfile(dist_info_dir, generator='egg2wheel') + wheel_name = '{name}-{ver}-{pyver}-{}-{}.whl'.format(abi, arch, **egg_info) + with WheelFile(os.path.join(dest_dir, wheel_name), 'w') as wf: + wf.write_files(dir) + + shutil.rmtree(dir) + + +def parse_wininst_info(wininfo_name, egginfo_name): + """Extract metadata from filenames. + + Extracts the 4 metadataitems needed (name, version, pyversion, arch) from + the installer filename and the name of the egg-info directory embedded in + the zipfile (if any). + + The egginfo filename has the format:: + + name-ver(-pyver)(-arch).egg-info + + The installer filename has the format:: + + name-ver.arch(-pyver).exe + + Some things to note: + + 1. The installer filename is not definitive. An installer can be renamed + and work perfectly well as an installer. So more reliable data should + be used whenever possible. + 2. The egg-info data should be preferred for the name and version, because + these come straight from the distutils metadata, and are mandatory. + 3. The pyver from the egg-info data should be ignored, as it is + constructed from the version of Python used to build the installer, + which is irrelevant - the installer filename is correct here (even to + the point that when it's not there, any version is implied). + 4. The architecture must be taken from the installer filename, as it is + not included in the egg-info data. + 5. Architecture-neutral installers still have an architecture because the + installer format itself (being executable) is architecture-specific. We + should therefore ignore the architecture if the content is pure-python. + """ + + egginfo = None + if egginfo_name: + egginfo = egg_info_re.search(egginfo_name) + if not egginfo: + raise ValueError("Egg info filename %s is not valid" % (egginfo_name,)) + + # Parse the wininst filename + # 1. Distribution name (up to the first '-') + w_name, sep, rest = wininfo_name.partition('-') + if not sep: + raise ValueError("Installer filename %s is not valid" % (wininfo_name,)) + + # Strip '.exe' + rest = rest[:-4] + # 2. Python version (from the last '-', must start with 'py') + rest2, sep, w_pyver = rest.rpartition('-') + if sep and w_pyver.startswith('py'): + rest = rest2 + w_pyver = w_pyver.replace('.', '') + else: + # Not version specific - use py2.py3. While it is possible that + # pure-Python code is not compatible with both Python 2 and 3, there + # is no way of knowing from the wininst format, so we assume the best + # here (the user can always manually rename the wheel to be more + # restrictive if needed). + w_pyver = 'py2.py3' + # 3. Version and architecture + w_ver, sep, w_arch = rest.rpartition('.') + if not sep: + raise ValueError("Installer filename %s is not valid" % (wininfo_name,)) + + if egginfo: + w_name = egginfo.group('name') + w_ver = egginfo.group('ver') + + return {'name': w_name, 'ver': w_ver, 'arch': w_arch, 'pyver': w_pyver} + + +def wininst2wheel(path, dest_dir): + with zipfile.ZipFile(path) as bdw: + # Search for egg-info in the archive + egginfo_name = None + for filename in bdw.namelist(): + if '.egg-info' in filename: + egginfo_name = filename + break + + info = parse_wininst_info(os.path.basename(path), egginfo_name) + + root_is_purelib = True + for zipinfo in bdw.infolist(): + if zipinfo.filename.startswith('PLATLIB'): + root_is_purelib = False + break + if root_is_purelib: + paths = {'purelib': ''} + else: + paths = {'platlib': ''} + + dist_info = "%(name)s-%(ver)s" % info + datadir = "%s.data/" % dist_info + + # rewrite paths to trick ZipFile into extracting an egg + # XXX grab wininst .ini - between .exe, padding, and first zip file. + members = [] + egginfo_name = '' + for zipinfo in bdw.infolist(): + key, basename = zipinfo.filename.split('/', 1) + key = key.lower() + basepath = paths.get(key, None) + if basepath is None: + basepath = datadir + key.lower() + '/' + oldname = zipinfo.filename + newname = basepath + basename + zipinfo.filename = newname + del bdw.NameToInfo[oldname] + bdw.NameToInfo[newname] = zipinfo + # Collect member names, but omit '' (from an entry like "PLATLIB/" + if newname: + members.append(newname) + # Remember egg-info name for the egg2dist call below + if not egginfo_name: + if newname.endswith('.egg-info'): + egginfo_name = newname + elif '.egg-info/' in newname: + egginfo_name, sep, _ = newname.rpartition('/') + dir = tempfile.mkdtemp(suffix="_b2w") + bdw.extractall(dir, members) + + # egg2wheel + abi = 'none' + pyver = info['pyver'] + arch = (info['arch'] or 'any').replace('.', '_').replace('-', '_') + # Wininst installers always have arch even if they are not + # architecture-specific (because the format itself is). + # So, assume the content is architecture-neutral if root is purelib. + if root_is_purelib: + arch = 'any' + # If the installer is architecture-specific, it's almost certainly also + # CPython-specific. + if arch != 'any': + pyver = pyver.replace('py', 'cp') + wheel_name = '-'.join((dist_info, pyver, abi, arch)) + if root_is_purelib: + bw = bdist_wheel(dist.Distribution()) + else: + bw = _bdist_wheel_tag(dist.Distribution()) + + bw.root_is_pure = root_is_purelib + bw.python_tag = pyver + bw.plat_name_supplied = True + bw.plat_name = info['arch'] or 'any' + + if not root_is_purelib: + bw.full_tag_supplied = True + bw.full_tag = (pyver, abi, arch) + + dist_info_dir = os.path.join(dir, '%s.dist-info' % dist_info) + bw.egg2dist(os.path.join(dir, egginfo_name), dist_info_dir) + bw.write_wheelfile(dist_info_dir, generator='wininst2wheel') + + wheel_path = os.path.join(dest_dir, wheel_name) + with WheelFile(wheel_path, 'w') as wf: + wf.write_files(dir) + + shutil.rmtree(dir) + + +def convert(files, dest_dir, verbose): + # Only support wheel convert if pkg_resources is present + require_pkgresources('wheel convert') + + for pat in files: + for installer in iglob(pat): + if os.path.splitext(installer)[1] == '.egg': + conv = egg2wheel + else: + conv = wininst2wheel + + if verbose: + print("{}... ".format(installer)) + sys.stdout.flush() + + conv(installer, dest_dir) + if verbose: + print("OK") diff --git a/robot/lib/python3.8/site-packages/wheel/cli/pack.py b/robot/lib/python3.8/site-packages/wheel/cli/pack.py new file mode 100644 index 0000000000000000000000000000000000000000..1e77fdbd2ccfb17ce1ca65f1834ac1dc927f3047 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/cli/pack.py @@ -0,0 +1,79 @@ +from __future__ import print_function + +import os.path +import re +import sys + +from wheel.cli import WheelError +from wheel.wheelfile import WheelFile + +DIST_INFO_RE = re.compile(r"^(?P(?P.+?)-(?P\d.*?))\.dist-info$") +BUILD_NUM_RE = re.compile(br'Build: (\d\w*)$') + + +def pack(directory, dest_dir, build_number): + """Repack a previously unpacked wheel directory into a new wheel file. + + The .dist-info/WHEEL file must contain one or more tags so that the target + wheel file name can be determined. + + :param directory: The unpacked wheel directory + :param dest_dir: Destination directory (defaults to the current directory) + """ + # Find the .dist-info directory + dist_info_dirs = [fn for fn in os.listdir(directory) + if os.path.isdir(os.path.join(directory, fn)) and DIST_INFO_RE.match(fn)] + if len(dist_info_dirs) > 1: + raise WheelError('Multiple .dist-info directories found in {}'.format(directory)) + elif not dist_info_dirs: + raise WheelError('No .dist-info directories found in {}'.format(directory)) + + # Determine the target wheel filename + dist_info_dir = dist_info_dirs[0] + name_version = DIST_INFO_RE.match(dist_info_dir).group('namever') + + # Read the tags and the existing build number from .dist-info/WHEEL + existing_build_number = None + wheel_file_path = os.path.join(directory, dist_info_dir, 'WHEEL') + with open(wheel_file_path) as f: + tags = [] + for line in f: + if line.startswith('Tag: '): + tags.append(line.split(' ')[1].rstrip()) + elif line.startswith('Build: '): + existing_build_number = line.split(' ')[1].rstrip() + + if not tags: + raise WheelError('No tags present in {}/WHEEL; cannot determine target wheel filename' + .format(dist_info_dir)) + + # Set the wheel file name and add/replace/remove the Build tag in .dist-info/WHEEL + build_number = build_number if build_number is not None else existing_build_number + if build_number is not None: + if build_number: + name_version += '-' + build_number + + if build_number != existing_build_number: + replacement = ('Build: %s\r\n' % build_number).encode('ascii') if build_number else b'' + with open(wheel_file_path, 'rb+') as f: + wheel_file_content = f.read() + if not BUILD_NUM_RE.subn(replacement, wheel_file_content)[1]: + wheel_file_content += replacement + + f.truncate() + f.write(wheel_file_content) + + # Reassemble the tags for the wheel file + impls = sorted({tag.split('-')[0] for tag in tags}) + abivers = sorted({tag.split('-')[1] for tag in tags}) + platforms = sorted({tag.split('-')[2] for tag in tags}) + tagline = '-'.join(['.'.join(impls), '.'.join(abivers), '.'.join(platforms)]) + + # Repack the wheel + wheel_path = os.path.join(dest_dir, '{}-{}.whl'.format(name_version, tagline)) + with WheelFile(wheel_path, 'w') as wf: + print("Repacking wheel as {}...".format(wheel_path), end='') + sys.stdout.flush() + wf.write_files(directory) + + print('OK') diff --git a/robot/lib/python3.8/site-packages/wheel/cli/unpack.py b/robot/lib/python3.8/site-packages/wheel/cli/unpack.py new file mode 100644 index 0000000000000000000000000000000000000000..2e9857a35088ebec8d0a51c4be09caa66320435e --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/cli/unpack.py @@ -0,0 +1,25 @@ +from __future__ import print_function + +import os.path +import sys + +from ..wheelfile import WheelFile + + +def unpack(path, dest='.'): + """Unpack a wheel. + + Wheel content will be unpacked to {dest}/{name}-{ver}, where {name} + is the package name and {ver} its version. + + :param path: The path to the wheel. + :param dest: Destination directory (default to current directory). + """ + with WheelFile(path) as wf: + namever = wf.parsed_filename.group('namever') + destination = os.path.join(dest, namever) + print("Unpacking to: {}...".format(destination), end='') + sys.stdout.flush() + wf.extractall(destination) + + print('OK') diff --git a/robot/lib/python3.8/site-packages/wheel/macosx_libfile.py b/robot/lib/python3.8/site-packages/wheel/macosx_libfile.py new file mode 100644 index 0000000000000000000000000000000000000000..a9036886734e619d6cff4c10a9fb0cfddf85e136 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/macosx_libfile.py @@ -0,0 +1,341 @@ +""" +This module contains function to analyse dynamic library +headers to extract system information + +Currently only for MacOSX + +Library file on macosx system starts with Mach-O or Fat field. +This can be distinguish by first 32 bites and it is called magic number. +Proper value of magic number is with suffix _MAGIC. Suffix _CIGAM means +reversed bytes order. +Both fields can occur in two types: 32 and 64 bytes. + +FAT field inform that this library contains few version of library +(typically for different types version). It contains +information where Mach-O headers starts. + +Each section started with Mach-O header contains one library +(So if file starts with this field it contains only one version). + +After filed Mach-O there are section fields. +Each of them starts with two fields: +cmd - magic number for this command +cmdsize - total size occupied by this section information. + +In this case only sections LC_VERSION_MIN_MACOSX (for macosx 10.13 and earlier) +and LC_BUILD_VERSION (for macosx 10.14 and newer) are interesting, +because them contains information about minimal system version. + +Important remarks: +- For fat files this implementation looks for maximum number version. + It not check if it is 32 or 64 and do not compare it with currently builded package. + So it is possible to false report higher version that needed. +- All structures signatures are taken form macosx header files. +- I think that binary format will be more stable than `otool` output. + and if apple introduce some changes both implementation will need to be updated. +""" + +import ctypes +import sys + +"""here the needed const and struct from mach-o header files""" + +FAT_MAGIC = 0xcafebabe +FAT_CIGAM = 0xbebafeca +FAT_MAGIC_64 = 0xcafebabf +FAT_CIGAM_64 = 0xbfbafeca +MH_MAGIC = 0xfeedface +MH_CIGAM = 0xcefaedfe +MH_MAGIC_64 = 0xfeedfacf +MH_CIGAM_64 = 0xcffaedfe + +LC_VERSION_MIN_MACOSX = 0x24 +LC_BUILD_VERSION = 0x32 + + +mach_header_fields = [ + ("magic", ctypes.c_uint32), ("cputype", ctypes.c_int), + ("cpusubtype", ctypes.c_int), ("filetype", ctypes.c_uint32), + ("ncmds", ctypes.c_uint32), ("sizeofcmds", ctypes.c_uint32), + ("flags", ctypes.c_uint32) + ] +""" +struct mach_header { + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ +}; +typedef integer_t cpu_type_t; +typedef integer_t cpu_subtype_t; +""" + +mach_header_fields_64 = mach_header_fields + [("reserved", ctypes.c_uint32)] +""" +struct mach_header_64 { + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ + uint32_t reserved; /* reserved */ +}; +""" + +fat_header_fields = [("magic", ctypes.c_uint32), ("nfat_arch", ctypes.c_uint32)] +""" +struct fat_header { + uint32_t magic; /* FAT_MAGIC or FAT_MAGIC_64 */ + uint32_t nfat_arch; /* number of structs that follow */ +}; +""" + +fat_arch_fields = [ + ("cputype", ctypes.c_int), ("cpusubtype", ctypes.c_int), + ("offset", ctypes.c_uint32), ("size", ctypes.c_uint32), + ("align", ctypes.c_uint32) +] +""" +struct fat_arch { + cpu_type_t cputype; /* cpu specifier (int) */ + cpu_subtype_t cpusubtype; /* machine specifier (int) */ + uint32_t offset; /* file offset to this object file */ + uint32_t size; /* size of this object file */ + uint32_t align; /* alignment as a power of 2 */ +}; +""" + +fat_arch_64_fields = [ + ("cputype", ctypes.c_int), ("cpusubtype", ctypes.c_int), + ("offset", ctypes.c_uint64), ("size", ctypes.c_uint64), + ("align", ctypes.c_uint32), ("reserved", ctypes.c_uint32) +] +""" +struct fat_arch_64 { + cpu_type_t cputype; /* cpu specifier (int) */ + cpu_subtype_t cpusubtype; /* machine specifier (int) */ + uint64_t offset; /* file offset to this object file */ + uint64_t size; /* size of this object file */ + uint32_t align; /* alignment as a power of 2 */ + uint32_t reserved; /* reserved */ +}; +""" + +segment_base_fields = [("cmd", ctypes.c_uint32), ("cmdsize", ctypes.c_uint32)] +"""base for reading segment info""" + +segment_command_fields = [ + ("cmd", ctypes.c_uint32), ("cmdsize", ctypes.c_uint32), + ("segname", ctypes.c_char * 16), ("vmaddr", ctypes.c_uint32), + ("vmsize", ctypes.c_uint32), ("fileoff", ctypes.c_uint32), + ("filesize", ctypes.c_uint32), ("maxprot", ctypes.c_int), + ("initprot", ctypes.c_int), ("nsects", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ] +""" +struct segment_command { /* for 32-bit architectures */ + uint32_t cmd; /* LC_SEGMENT */ + uint32_t cmdsize; /* includes sizeof section structs */ + char segname[16]; /* segment name */ + uint32_t vmaddr; /* memory address of this segment */ + uint32_t vmsize; /* memory size of this segment */ + uint32_t fileoff; /* file offset of this segment */ + uint32_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; +typedef int vm_prot_t; +""" + +segment_command_fields_64 = [ + ("cmd", ctypes.c_uint32), ("cmdsize", ctypes.c_uint32), + ("segname", ctypes.c_char * 16), ("vmaddr", ctypes.c_uint64), + ("vmsize", ctypes.c_uint64), ("fileoff", ctypes.c_uint64), + ("filesize", ctypes.c_uint64), ("maxprot", ctypes.c_int), + ("initprot", ctypes.c_int), ("nsects", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ] +""" +struct segment_command_64 { /* for 64-bit architectures */ + uint32_t cmd; /* LC_SEGMENT_64 */ + uint32_t cmdsize; /* includes sizeof section_64 structs */ + char segname[16]; /* segment name */ + uint64_t vmaddr; /* memory address of this segment */ + uint64_t vmsize; /* memory size of this segment */ + uint64_t fileoff; /* file offset of this segment */ + uint64_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; +""" + +version_min_command_fields = segment_base_fields + \ + [("version", ctypes.c_uint32), ("sdk", ctypes.c_uint32)] +""" +struct version_min_command { + uint32_t cmd; /* LC_VERSION_MIN_MACOSX or + LC_VERSION_MIN_IPHONEOS or + LC_VERSION_MIN_WATCHOS or + LC_VERSION_MIN_TVOS */ + uint32_t cmdsize; /* sizeof(struct min_version_command) */ + uint32_t version; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ +}; +""" + +build_version_command_fields = segment_base_fields + \ + [("platform", ctypes.c_uint32), ("minos", ctypes.c_uint32), + ("sdk", ctypes.c_uint32), ("ntools", ctypes.c_uint32)] +""" +struct build_version_command { + uint32_t cmd; /* LC_BUILD_VERSION */ + uint32_t cmdsize; /* sizeof(struct build_version_command) plus */ + /* ntools * sizeof(struct build_tool_version) */ + uint32_t platform; /* platform */ + uint32_t minos; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t ntools; /* number of tool entries following this */ +}; +""" + + +def swap32(x): + return (((x << 24) & 0xFF000000) | + ((x << 8) & 0x00FF0000) | + ((x >> 8) & 0x0000FF00) | + ((x >> 24) & 0x000000FF)) + + +def get_base_class_and_magic_number(lib_file, seek=None): + if seek is None: + seek = lib_file.tell() + else: + lib_file.seek(seek) + magic_number = ctypes.c_uint32.from_buffer_copy( + lib_file.read(ctypes.sizeof(ctypes.c_uint32))).value + + # Handle wrong byte order + if magic_number in [FAT_CIGAM, FAT_CIGAM_64, MH_CIGAM, MH_CIGAM_64]: + if sys.byteorder == "little": + BaseClass = ctypes.BigEndianStructure + else: + BaseClass = ctypes.LittleEndianStructure + + magic_number = swap32(magic_number) + else: + BaseClass = ctypes.Structure + + lib_file.seek(seek) + return BaseClass, magic_number + + +def read_data(struct_class, lib_file): + return struct_class.from_buffer_copy(lib_file.read( + ctypes.sizeof(struct_class))) + + +def extract_macosx_min_system_version(path_to_lib): + with open(path_to_lib, "rb") as lib_file: + BaseClass, magic_number = get_base_class_and_magic_number(lib_file, 0) + if magic_number not in [FAT_MAGIC, FAT_MAGIC_64, MH_MAGIC, MH_MAGIC_64]: + return + + if magic_number in [FAT_MAGIC, FAT_CIGAM_64]: + class FatHeader(BaseClass): + _fields_ = fat_header_fields + + fat_header = read_data(FatHeader, lib_file) + if magic_number == FAT_MAGIC: + + class FatArch(BaseClass): + _fields_ = fat_arch_fields + else: + + class FatArch(BaseClass): + _fields_ = fat_arch_64_fields + + fat_arch_list = [read_data(FatArch, lib_file) for _ in range(fat_header.nfat_arch)] + + versions_list = [] + for el in fat_arch_list: + try: + version = read_mach_header(lib_file, el.offset) + if version is not None: + versions_list.append(version) + except ValueError: + pass + + if len(versions_list) > 0: + return max(versions_list) + else: + return None + + else: + try: + return read_mach_header(lib_file, 0) + except ValueError: + """when some error during read library files""" + return None + + +def read_mach_header(lib_file, seek=None): + """ + This funcition parse mach-O header and extract + information about minimal system version + + :param lib_file: reference to opened library file with pointer + """ + if seek is not None: + lib_file.seek(seek) + base_class, magic_number = get_base_class_and_magic_number(lib_file) + arch = "32" if magic_number == MH_MAGIC else "64" + + class SegmentBase(base_class): + _fields_ = segment_base_fields + + if arch == "32": + + class MachHeader(base_class): + _fields_ = mach_header_fields + + else: + + class MachHeader(base_class): + _fields_ = mach_header_fields_64 + + mach_header = read_data(MachHeader, lib_file) + for _i in range(mach_header.ncmds): + pos = lib_file.tell() + segment_base = read_data(SegmentBase, lib_file) + lib_file.seek(pos) + if segment_base.cmd == LC_VERSION_MIN_MACOSX: + class VersionMinCommand(base_class): + _fields_ = version_min_command_fields + + version_info = read_data(VersionMinCommand, lib_file) + return parse_version(version_info.version) + elif segment_base.cmd == LC_BUILD_VERSION: + class VersionBuild(base_class): + _fields_ = build_version_command_fields + + version_info = read_data(VersionBuild, lib_file) + return parse_version(version_info.minos) + else: + lib_file.seek(pos + segment_base.cmdsize) + continue + + +def parse_version(version): + x = (version & 0xffff0000) >> 16 + y = (version & 0x0000ff00) >> 8 + z = (version & 0x000000ff) + return x, y, z diff --git a/robot/lib/python3.8/site-packages/wheel/metadata.py b/robot/lib/python3.8/site-packages/wheel/metadata.py new file mode 100644 index 0000000000000000000000000000000000000000..bbf57a77041d70abff0ba2a1d4e1951d8ec2cc6c --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/metadata.py @@ -0,0 +1,138 @@ +""" +Tools for converting old- to new-style metadata. +""" + +import os.path +import re +import textwrap + +import pkg_resources + +from .pkginfo import read_pkg_info + +# Support markers syntax with the extra at the end only +EXTRA_RE = re.compile( + r"""^(?P.*?)(;\s*(?P.*?)(extra == '(?P.*?)')?)$""") + + +def requires_to_requires_dist(requirement): + """Return the version specifier for a requirement in PEP 345/566 fashion.""" + if getattr(requirement, 'url', None): + return " @ " + requirement.url + + requires_dist = [] + for op, ver in requirement.specs: + requires_dist.append(op + ver) + if not requires_dist: + return '' + return " (%s)" % ','.join(sorted(requires_dist)) + + +def convert_requirements(requirements): + """Yield Requires-Dist: strings for parsed requirements strings.""" + for req in requirements: + parsed_requirement = pkg_resources.Requirement.parse(req) + spec = requires_to_requires_dist(parsed_requirement) + extras = ",".join(sorted(parsed_requirement.extras)) + if extras: + extras = "[%s]" % extras + yield (parsed_requirement.project_name + extras + spec) + + +def generate_requirements(extras_require): + """ + Convert requirements from a setup()-style dictionary to ('Requires-Dist', 'requirement') + and ('Provides-Extra', 'extra') tuples. + + extras_require is a dictionary of {extra: [requirements]} as passed to setup(), + using the empty extra {'': [requirements]} to hold install_requires. + """ + for extra, depends in extras_require.items(): + condition = '' + extra = extra or '' + if ':' in extra: # setuptools extra:condition syntax + extra, condition = extra.split(':', 1) + + extra = pkg_resources.safe_extra(extra) + if extra: + yield 'Provides-Extra', extra + if condition: + condition = "(" + condition + ") and " + condition += "extra == '%s'" % extra + + if condition: + condition = ' ; ' + condition + + for new_req in convert_requirements(depends): + yield 'Requires-Dist', new_req + condition + + +def pkginfo_to_metadata(egg_info_path, pkginfo_path): + """ + Convert .egg-info directory with PKG-INFO to the Metadata 2.1 format + """ + pkg_info = read_pkg_info(pkginfo_path) + pkg_info.replace_header('Metadata-Version', '2.1') + # Those will be regenerated from `requires.txt`. + del pkg_info['Provides-Extra'] + del pkg_info['Requires-Dist'] + requires_path = os.path.join(egg_info_path, 'requires.txt') + if os.path.exists(requires_path): + with open(requires_path) as requires_file: + requires = requires_file.read() + + parsed_requirements = sorted(pkg_resources.split_sections(requires), + key=lambda x: x[0] or '') + for extra, reqs in parsed_requirements: + for key, value in generate_requirements({extra: reqs}): + if (key, value) not in pkg_info.items(): + pkg_info[key] = value + + description = pkg_info['Description'] + if description: + pkg_info.set_payload(dedent_description(pkg_info)) + del pkg_info['Description'] + + return pkg_info + + +def pkginfo_unicode(pkg_info, field): + """Hack to coax Unicode out of an email Message() - Python 3.3+""" + text = pkg_info[field] + field = field.lower() + if not isinstance(text, str): + for item in pkg_info.raw_items(): + if item[0].lower() == field: + text = item[1].encode('ascii', 'surrogateescape') \ + .decode('utf-8') + break + + return text + + +def dedent_description(pkg_info): + """ + Dedent and convert pkg_info['Description'] to Unicode. + """ + description = pkg_info['Description'] + + # Python 3 Unicode handling, sorta. + surrogates = False + if not isinstance(description, str): + surrogates = True + description = pkginfo_unicode(pkg_info, 'Description') + + description_lines = description.splitlines() + description_dedent = '\n'.join( + # if the first line of long_description is blank, + # the first line here will be indented. + (description_lines[0].lstrip(), + textwrap.dedent('\n'.join(description_lines[1:])), + '\n')) + + if surrogates: + description_dedent = description_dedent \ + .encode("utf8") \ + .decode("ascii", "surrogateescape") + + return description_dedent diff --git a/robot/lib/python3.8/site-packages/wheel/pep425tags.py b/robot/lib/python3.8/site-packages/wheel/pep425tags.py new file mode 100644 index 0000000000000000000000000000000000000000..0c2576361d9c166d4e3249a718b0c51f963832f6 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/pep425tags.py @@ -0,0 +1,261 @@ +"""Generate and work with PEP 425 Compatibility Tags.""" + +import distutils.util +import platform +import sys +import os +import sysconfig +import warnings + +from .macosx_libfile import extract_macosx_min_system_version + + +try: + from importlib.machinery import all_suffixes as get_all_suffixes +except ImportError: + from imp import get_suffixes + + def get_all_suffixes(): + return [suffix[0] for suffix in get_suffixes()] + + +def get_config_var(var): + try: + return sysconfig.get_config_var(var) + except IOError as e: # pip Issue #1074 + warnings.warn("{0}".format(e), RuntimeWarning) + return None + + +def get_abbr_impl(): + """Return abbreviated implementation name.""" + impl = platform.python_implementation() + if impl == 'PyPy': + return 'pp' + elif impl == 'Jython': + return 'jy' + elif impl == 'IronPython': + return 'ip' + elif impl == 'CPython': + return 'cp' + + raise LookupError('Unknown Python implementation: ' + impl) + + +def get_impl_ver(): + """Return implementation version.""" + impl_ver = get_config_var("py_version_nodot") + if not impl_ver: + impl_ver = ''.join(map(str, get_impl_version_info())) + return impl_ver + + +def get_impl_version_info(): + """Return sys.version_info-like tuple for use in decrementing the minor + version.""" + return sys.version_info[0], sys.version_info[1] + + +def get_flag(var, fallback, expected=True, warn=True): + """Use a fallback method for determining SOABI flags if the needed config + var is unset or unavailable.""" + val = get_config_var(var) + if val is None: + if warn: + warnings.warn("Config variable '{0}' is unset, Python ABI tag may " + "be incorrect".format(var), RuntimeWarning, 2) + return fallback() + return val == expected + + +def get_abi_tag(): + """Return the ABI tag based on SOABI (if available) or emulate SOABI + (CPython 2, PyPy).""" + soabi = get_config_var('SOABI') + impl = get_abbr_impl() + if not soabi and impl in ('cp', 'pp') and hasattr(sys, 'maxunicode'): + d = '' + m = '' + u = '' + if get_flag('Py_DEBUG', + lambda: hasattr(sys, 'gettotalrefcount'), + warn=(impl == 'cp')): + d = 'd' + if get_flag('WITH_PYMALLOC', + lambda: impl == 'cp', + warn=(impl == 'cp' and + sys.version_info < (3, 8))) \ + and sys.version_info < (3, 8): + m = 'm' + if get_flag('Py_UNICODE_SIZE', + lambda: sys.maxunicode == 0x10ffff, + expected=4, + warn=(impl == 'cp' and + sys.version_info < (3, 3))) \ + and sys.version_info < (3, 3): + u = 'u' + abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u) + elif soabi and soabi.startswith('cpython-'): + abi = 'cp' + soabi.split('-')[1] + elif soabi: + abi = soabi.replace('.', '_').replace('-', '_') + else: + abi = None + return abi + + +def calculate_macosx_platform_tag(archive_root, platform_tag): + """ + Calculate proper macosx platform tag basing on files which are included to wheel + + Example platform tag `macosx-10.14-x86_64` + """ + prefix, base_version, suffix = platform_tag.split('-') + base_version = tuple([int(x) for x in base_version.split(".")]) + if len(base_version) >= 2: + base_version = base_version[0:2] + + assert len(base_version) == 2 + if "MACOSX_DEPLOYMENT_TARGET" in os.environ: + deploy_target = tuple([int(x) for x in os.environ[ + "MACOSX_DEPLOYMENT_TARGET"].split(".")]) + if len(deploy_target) >= 2: + deploy_target = deploy_target[0:2] + if deploy_target < base_version: + sys.stderr.write( + "[WARNING] MACOSX_DEPLOYMENT_TARGET is set to a lower value ({}) than the " + "version on which the Python interpreter was compiled ({}), and will be " + "ignored.\n".format('.'.join(str(x) for x in deploy_target), + '.'.join(str(x) for x in base_version)) + ) + else: + base_version = deploy_target + + assert len(base_version) == 2 + start_version = base_version + versions_dict = {} + for (dirpath, dirnames, filenames) in os.walk(archive_root): + for filename in filenames: + if filename.endswith('.dylib') or filename.endswith('.so'): + lib_path = os.path.join(dirpath, filename) + min_ver = extract_macosx_min_system_version(lib_path) + if min_ver is not None: + versions_dict[lib_path] = min_ver[0:2] + + if len(versions_dict) > 0: + base_version = max(base_version, max(versions_dict.values())) + + # macosx platform tag do not support minor bugfix release + fin_base_version = "_".join([str(x) for x in base_version]) + if start_version < base_version: + problematic_files = [k for k, v in versions_dict.items() if v > start_version] + problematic_files = "\n".join(problematic_files) + if len(problematic_files) == 1: + files_form = "this file" + else: + files_form = "these files" + error_message = \ + "[WARNING] This wheel needs a higher macOS version than {} " \ + "To silence this warning, set MACOSX_DEPLOYMENT_TARGET to at least " +\ + fin_base_version + " or recreate " + files_form + " with lower " \ + "MACOSX_DEPLOYMENT_TARGET: \n" + problematic_files + + if "MACOSX_DEPLOYMENT_TARGET" in os.environ: + error_message = error_message.format("is set in MACOSX_DEPLOYMENT_TARGET variable.") + else: + error_message = error_message.format( + "the version your Python interpreter is compiled against.") + + sys.stderr.write(error_message) + + platform_tag = prefix + "_" + fin_base_version + "_" + suffix + return platform_tag + + +def get_platform(archive_root): + """Return our platform name 'win32', 'linux_x86_64'""" + # XXX remove distutils dependency + result = distutils.util.get_platform() + if result.startswith("macosx") and archive_root is not None: + result = calculate_macosx_platform_tag(archive_root, result) + result = result.replace('.', '_').replace('-', '_') + if result == "linux_x86_64" and sys.maxsize == 2147483647: + # pip pull request #3497 + result = "linux_i686" + + return result + + +def get_supported(archive_root, versions=None, supplied_platform=None): + """Return a list of supported tags for each version specified in + `versions`. + + :param versions: a list of string versions, of the form ["33", "32"], + or None. The first version will be assumed to support our ABI. + """ + supported = [] + + # Versions must be given with respect to the preference + if versions is None: + versions = [] + version_info = get_impl_version_info() + major = version_info[:-1] + # Support all previous minor Python versions. + for minor in range(version_info[-1], -1, -1): + versions.append(''.join(map(str, major + (minor,)))) + + impl = get_abbr_impl() + + abis = [] + + abi = get_abi_tag() + if abi: + abis[0:0] = [abi] + + abi3s = set() + for suffix in get_all_suffixes(): + if suffix.startswith('.abi'): + abi3s.add(suffix.split('.', 2)[1]) + + abis.extend(sorted(list(abi3s))) + + abis.append('none') + + platforms = [] + if supplied_platform: + platforms.append(supplied_platform) + platforms.append(get_platform(archive_root)) + + # Current version, current API (built specifically for our Python): + for abi in abis: + for arch in platforms: + supported.append(('%s%s' % (impl, versions[0]), abi, arch)) + + # abi3 modules compatible with older version of Python + for version in versions[1:]: + # abi3 was introduced in Python 3.2 + if version in ('31', '30'): + break + for abi in abi3s: # empty set if not Python 3 + for arch in platforms: + supported.append(("%s%s" % (impl, version), abi, arch)) + + # No abi / arch, but requires our implementation: + for i, version in enumerate(versions): + supported.append(('%s%s' % (impl, version), 'none', 'any')) + if i == 0: + # Tagged specifically as being cross-version compatible + # (with just the major version specified) + supported.append(('%s%s' % (impl, versions[0][0]), 'none', 'any')) + + # Major Python version + platform; e.g. binaries not using the Python API + for arch in platforms: + supported.append(('py%s' % (versions[0][0]), 'none', arch)) + + # No abi / arch, generic Python + for i, version in enumerate(versions): + supported.append(('py%s' % (version,), 'none', 'any')) + if i == 0: + supported.append(('py%s' % (version[0]), 'none', 'any')) + + return supported diff --git a/robot/lib/python3.8/site-packages/wheel/pkginfo.py b/robot/lib/python3.8/site-packages/wheel/pkginfo.py new file mode 100644 index 0000000000000000000000000000000000000000..115be45bdfb5bf1a6c9dca87471c5ca0839f63b1 --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/pkginfo.py @@ -0,0 +1,43 @@ +"""Tools for reading and writing PKG-INFO / METADATA without caring +about the encoding.""" + +from email.parser import Parser + +try: + unicode + _PY3 = False +except NameError: + _PY3 = True + +if not _PY3: + from email.generator import Generator + + def read_pkg_info_bytes(bytestr): + return Parser().parsestr(bytestr) + + def read_pkg_info(path): + with open(path, "r") as headers: + message = Parser().parse(headers) + return message + + def write_pkg_info(path, message): + with open(path, 'w') as metadata: + Generator(metadata, mangle_from_=False, maxheaderlen=0).flatten(message) +else: + from email.generator import BytesGenerator + + def read_pkg_info_bytes(bytestr): + headers = bytestr.decode(encoding="ascii", errors="surrogateescape") + message = Parser().parsestr(headers) + return message + + def read_pkg_info(path): + with open(path, "r", + encoding="ascii", + errors="surrogateescape") as headers: + message = Parser().parse(headers) + return message + + def write_pkg_info(path, message): + with open(path, "wb") as out: + BytesGenerator(out, mangle_from_=False, maxheaderlen=0).flatten(message) diff --git a/robot/lib/python3.8/site-packages/wheel/util.py b/robot/lib/python3.8/site-packages/wheel/util.py new file mode 100644 index 0000000000000000000000000000000000000000..3ae2b4457ca50752977907ec3cd3c0b7d27042ac --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/util.py @@ -0,0 +1,46 @@ +import base64 +import io +import sys + + +if sys.version_info[0] < 3: + text_type = unicode # noqa: F821 + + StringIO = io.BytesIO + + def native(s, encoding='utf-8'): + if isinstance(s, unicode): # noqa: F821 + return s.encode(encoding) + return s +else: + text_type = str + + StringIO = io.StringIO + + def native(s, encoding='utf-8'): + if isinstance(s, bytes): + return s.decode(encoding) + return s + + +def urlsafe_b64encode(data): + """urlsafe_b64encode without padding""" + return base64.urlsafe_b64encode(data).rstrip(b'=') + + +def urlsafe_b64decode(data): + """urlsafe_b64decode without padding""" + pad = b'=' * (4 - (len(data) & 3)) + return base64.urlsafe_b64decode(data + pad) + + +def as_unicode(s): + if isinstance(s, bytes): + return s.decode('utf-8') + return s + + +def as_bytes(s): + if isinstance(s, text_type): + return s.encode('utf-8') + return s diff --git a/robot/lib/python3.8/site-packages/wheel/wheelfile.py b/robot/lib/python3.8/site-packages/wheel/wheelfile.py new file mode 100644 index 0000000000000000000000000000000000000000..acc5dab512b488cc83d8c44cac4ee20310b131ee --- /dev/null +++ b/robot/lib/python3.8/site-packages/wheel/wheelfile.py @@ -0,0 +1,169 @@ +from __future__ import print_function + +import csv +import hashlib +import os.path +import re +import stat +import time +from collections import OrderedDict +from distutils import log as logger +from zipfile import ZIP_DEFLATED, ZipInfo, ZipFile + +from wheel.cli import WheelError +from wheel.util import urlsafe_b64decode, as_unicode, native, urlsafe_b64encode, as_bytes, StringIO + +# Non-greedy matching of an optional build number may be too clever (more +# invalid wheel filenames will match). Separate regex for .dist-info? +WHEEL_INFO_RE = re.compile( + r"""^(?P(?P.+?)-(?P.+?))(-(?P\d[^-]*))? + -(?P.+?)-(?P.+?)-(?P.+?)\.whl$""", + re.VERBOSE) + + +def get_zipinfo_datetime(timestamp=None): + # Some applications need reproducible .whl files, but they can't do this without forcing + # the timestamp of the individual ZipInfo objects. See issue #143. + timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timestamp or time.time())) + return time.gmtime(timestamp)[0:6] + + +class WheelFile(ZipFile): + """A ZipFile derivative class that also reads SHA-256 hashes from + .dist-info/RECORD and checks any read files against those. + """ + + _default_algorithm = hashlib.sha256 + + def __init__(self, file, mode='r', compression=ZIP_DEFLATED): + basename = os.path.basename(file) + self.parsed_filename = WHEEL_INFO_RE.match(basename) + if not basename.endswith('.whl') or self.parsed_filename is None: + raise WheelError("Bad wheel filename {!r}".format(basename)) + + ZipFile.__init__(self, file, mode, compression=compression, allowZip64=True) + + self.dist_info_path = '{}.dist-info'.format(self.parsed_filename.group('namever')) + self.record_path = self.dist_info_path + '/RECORD' + self._file_hashes = OrderedDict() + self._file_sizes = {} + if mode == 'r': + # Ignore RECORD and any embedded wheel signatures + self._file_hashes[self.record_path] = None, None + self._file_hashes[self.record_path + '.jws'] = None, None + self._file_hashes[self.record_path + '.p7s'] = None, None + + # Fill in the expected hashes by reading them from RECORD + try: + record = self.open(self.record_path) + except KeyError: + raise WheelError('Missing {} file'.format(self.record_path)) + + with record: + for line in record: + line = line.decode('utf-8') + path, hash_sum, size = line.rsplit(u',', 2) + if hash_sum: + algorithm, hash_sum = hash_sum.split(u'=') + try: + hashlib.new(algorithm) + except ValueError: + raise WheelError('Unsupported hash algorithm: {}'.format(algorithm)) + + if algorithm.lower() in {'md5', 'sha1'}: + raise WheelError( + 'Weak hash algorithm ({}) is not permitted by PEP 427' + .format(algorithm)) + + self._file_hashes[path] = ( + algorithm, urlsafe_b64decode(hash_sum.encode('ascii'))) + + def open(self, name_or_info, mode="r", pwd=None): + def _update_crc(newdata, eof=None): + if eof is None: + eof = ef._eof + update_crc_orig(newdata) + else: # Python 2 + update_crc_orig(newdata, eof) + + running_hash.update(newdata) + if eof and running_hash.digest() != expected_hash: + raise WheelError("Hash mismatch for file '{}'".format(native(ef_name))) + + ef = ZipFile.open(self, name_or_info, mode, pwd) + ef_name = as_unicode(name_or_info.filename if isinstance(name_or_info, ZipInfo) + else name_or_info) + if mode == 'r' and not ef_name.endswith('/'): + if ef_name not in self._file_hashes: + raise WheelError("No hash found for file '{}'".format(native(ef_name))) + + algorithm, expected_hash = self._file_hashes[ef_name] + if expected_hash is not None: + # Monkey patch the _update_crc method to also check for the hash from RECORD + running_hash = hashlib.new(algorithm) + update_crc_orig, ef._update_crc = ef._update_crc, _update_crc + + return ef + + def write_files(self, base_dir): + logger.info("creating '%s' and adding '%s' to it", self.filename, base_dir) + deferred = [] + for root, dirnames, filenames in os.walk(base_dir): + # Sort the directory names so that `os.walk` will walk them in a + # defined order on the next iteration. + dirnames.sort() + for name in sorted(filenames): + path = os.path.normpath(os.path.join(root, name)) + if os.path.isfile(path): + arcname = os.path.relpath(path, base_dir).replace(os.path.sep, '/') + if arcname == self.record_path: + pass + elif root.endswith('.dist-info'): + deferred.append((path, arcname)) + else: + self.write(path, arcname) + + deferred.sort() + for path, arcname in deferred: + self.write(path, arcname) + + def write(self, filename, arcname=None, compress_type=None): + with open(filename, 'rb') as f: + st = os.fstat(f.fileno()) + data = f.read() + + zinfo = ZipInfo(arcname or filename, date_time=get_zipinfo_datetime(st.st_mtime)) + zinfo.external_attr = (stat.S_IMODE(st.st_mode) | stat.S_IFMT(st.st_mode)) << 16 + zinfo.compress_type = compress_type or self.compression + self.writestr(zinfo, data, compress_type) + + def writestr(self, zinfo_or_arcname, bytes, compress_type=None): + ZipFile.writestr(self, zinfo_or_arcname, bytes, compress_type) + fname = (zinfo_or_arcname.filename if isinstance(zinfo_or_arcname, ZipInfo) + else zinfo_or_arcname) + logger.info("adding '%s'", fname) + if fname != self.record_path: + hash_ = self._default_algorithm(bytes) + self._file_hashes[fname] = hash_.name, native(urlsafe_b64encode(hash_.digest())) + self._file_sizes[fname] = len(bytes) + + def close(self): + # Write RECORD + if self.fp is not None and self.mode == 'w' and self._file_hashes: + data = StringIO() + writer = csv.writer(data, delimiter=',', quotechar='"', lineterminator='\n') + writer.writerows(( + ( + fname, + algorithm + "=" + hash_, + self._file_sizes[fname] + ) + for fname, (algorithm, hash_) in self._file_hashes.items() + )) + writer.writerow((format(self.record_path), "", "")) + zinfo = ZipInfo(native(self.record_path), date_time=get_zipinfo_datetime()) + zinfo.compress_type = self.compression + zinfo.external_attr = 0o664 << 16 + self.writestr(zinfo, as_bytes(data.getvalue())) + + ZipFile.close(self) diff --git a/robot/lib/python3.8/site-packages/yaml/__init__.py b/robot/lib/python3.8/site-packages/yaml/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..13d687c501c854057404469446c680231fbc92e5 --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/__init__.py @@ -0,0 +1,427 @@ + +from .error import * + +from .tokens import * +from .events import * +from .nodes import * + +from .loader import * +from .dumper import * + +__version__ = '5.3.1' +try: + from .cyaml import * + __with_libyaml__ = True +except ImportError: + __with_libyaml__ = False + +import io + +#------------------------------------------------------------------------------ +# Warnings control +#------------------------------------------------------------------------------ + +# 'Global' warnings state: +_warnings_enabled = { + 'YAMLLoadWarning': True, +} + +# Get or set global warnings' state +def warnings(settings=None): + if settings is None: + return _warnings_enabled + + if type(settings) is dict: + for key in settings: + if key in _warnings_enabled: + _warnings_enabled[key] = settings[key] + +# Warn when load() is called without Loader=... +class YAMLLoadWarning(RuntimeWarning): + pass + +def load_warning(method): + if _warnings_enabled['YAMLLoadWarning'] is False: + return + + import warnings + + message = ( + "calling yaml.%s() without Loader=... is deprecated, as the " + "default Loader is unsafe. Please read " + "https://msg.pyyaml.org/load for full details." + ) % method + + warnings.warn(message, YAMLLoadWarning, stacklevel=3) + +#------------------------------------------------------------------------------ +def scan(stream, Loader=Loader): + """ + Scan a YAML stream and produce scanning tokens. + """ + loader = Loader(stream) + try: + while loader.check_token(): + yield loader.get_token() + finally: + loader.dispose() + +def parse(stream, Loader=Loader): + """ + Parse a YAML stream and produce parsing events. + """ + loader = Loader(stream) + try: + while loader.check_event(): + yield loader.get_event() + finally: + loader.dispose() + +def compose(stream, Loader=Loader): + """ + Parse the first YAML document in a stream + and produce the corresponding representation tree. + """ + loader = Loader(stream) + try: + return loader.get_single_node() + finally: + loader.dispose() + +def compose_all(stream, Loader=Loader): + """ + Parse all YAML documents in a stream + and produce corresponding representation trees. + """ + loader = Loader(stream) + try: + while loader.check_node(): + yield loader.get_node() + finally: + loader.dispose() + +def load(stream, Loader=None): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + """ + if Loader is None: + load_warning('load') + Loader = FullLoader + + loader = Loader(stream) + try: + return loader.get_single_data() + finally: + loader.dispose() + +def load_all(stream, Loader=None): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + """ + if Loader is None: + load_warning('load_all') + Loader = FullLoader + + loader = Loader(stream) + try: + while loader.check_data(): + yield loader.get_data() + finally: + loader.dispose() + +def full_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve all tags except those known to be + unsafe on untrusted input. + """ + return load(stream, FullLoader) + +def full_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve all tags except those known to be + unsafe on untrusted input. + """ + return load_all(stream, FullLoader) + +def safe_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve only basic YAML tags. This is known + to be safe for untrusted input. + """ + return load(stream, SafeLoader) + +def safe_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve only basic YAML tags. This is known + to be safe for untrusted input. + """ + return load_all(stream, SafeLoader) + +def unsafe_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve all tags, even those known to be + unsafe on untrusted input. + """ + return load(stream, UnsafeLoader) + +def unsafe_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve all tags, even those known to be + unsafe on untrusted input. + """ + return load_all(stream, UnsafeLoader) + +def emit(events, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None): + """ + Emit YAML parsing events into a stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + stream = io.StringIO() + getvalue = stream.getvalue + dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + try: + for event in events: + dumper.emit(event) + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def serialize_all(nodes, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None): + """ + Serialize a sequence of representation trees into a YAML stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + if encoding is None: + stream = io.StringIO() + else: + stream = io.BytesIO() + getvalue = stream.getvalue + dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end) + try: + dumper.open() + for node in nodes: + dumper.serialize(node) + dumper.close() + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def serialize(node, stream=None, Dumper=Dumper, **kwds): + """ + Serialize a representation tree into a YAML stream. + If stream is None, return the produced string instead. + """ + return serialize_all([node], stream, Dumper=Dumper, **kwds) + +def dump_all(documents, stream=None, Dumper=Dumper, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + """ + Serialize a sequence of Python objects into a YAML stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + if encoding is None: + stream = io.StringIO() + else: + stream = io.BytesIO() + getvalue = stream.getvalue + dumper = Dumper(stream, default_style=default_style, + default_flow_style=default_flow_style, + canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end, sort_keys=sort_keys) + try: + dumper.open() + for data in documents: + dumper.represent(data) + dumper.close() + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def dump(data, stream=None, Dumper=Dumper, **kwds): + """ + Serialize a Python object into a YAML stream. + If stream is None, return the produced string instead. + """ + return dump_all([data], stream, Dumper=Dumper, **kwds) + +def safe_dump_all(documents, stream=None, **kwds): + """ + Serialize a sequence of Python objects into a YAML stream. + Produce only basic YAML tags. + If stream is None, return the produced string instead. + """ + return dump_all(documents, stream, Dumper=SafeDumper, **kwds) + +def safe_dump(data, stream=None, **kwds): + """ + Serialize a Python object into a YAML stream. + Produce only basic YAML tags. + If stream is None, return the produced string instead. + """ + return dump_all([data], stream, Dumper=SafeDumper, **kwds) + +def add_implicit_resolver(tag, regexp, first=None, + Loader=None, Dumper=Dumper): + """ + Add an implicit scalar detector. + If an implicit scalar value matches the given regexp, + the corresponding tag is assigned to the scalar. + first is a sequence of possible initial characters or None. + """ + if Loader is None: + loader.Loader.add_implicit_resolver(tag, regexp, first) + loader.FullLoader.add_implicit_resolver(tag, regexp, first) + loader.UnsafeLoader.add_implicit_resolver(tag, regexp, first) + else: + Loader.add_implicit_resolver(tag, regexp, first) + Dumper.add_implicit_resolver(tag, regexp, first) + +def add_path_resolver(tag, path, kind=None, Loader=None, Dumper=Dumper): + """ + Add a path based resolver for the given tag. + A path is a list of keys that forms a path + to a node in the representation tree. + Keys can be string values, integers, or None. + """ + if Loader is None: + loader.Loader.add_path_resolver(tag, path, kind) + loader.FullLoader.add_path_resolver(tag, path, kind) + loader.UnsafeLoader.add_path_resolver(tag, path, kind) + else: + Loader.add_path_resolver(tag, path, kind) + Dumper.add_path_resolver(tag, path, kind) + +def add_constructor(tag, constructor, Loader=None): + """ + Add a constructor for the given tag. + Constructor is a function that accepts a Loader instance + and a node object and produces the corresponding Python object. + """ + if Loader is None: + loader.Loader.add_constructor(tag, constructor) + loader.FullLoader.add_constructor(tag, constructor) + loader.UnsafeLoader.add_constructor(tag, constructor) + else: + Loader.add_constructor(tag, constructor) + +def add_multi_constructor(tag_prefix, multi_constructor, Loader=None): + """ + Add a multi-constructor for the given tag prefix. + Multi-constructor is called for a node if its tag starts with tag_prefix. + Multi-constructor accepts a Loader instance, a tag suffix, + and a node object and produces the corresponding Python object. + """ + if Loader is None: + loader.Loader.add_multi_constructor(tag_prefix, multi_constructor) + loader.FullLoader.add_multi_constructor(tag_prefix, multi_constructor) + loader.UnsafeLoader.add_multi_constructor(tag_prefix, multi_constructor) + else: + Loader.add_multi_constructor(tag_prefix, multi_constructor) + +def add_representer(data_type, representer, Dumper=Dumper): + """ + Add a representer for the given type. + Representer is a function accepting a Dumper instance + and an instance of the given data type + and producing the corresponding representation node. + """ + Dumper.add_representer(data_type, representer) + +def add_multi_representer(data_type, multi_representer, Dumper=Dumper): + """ + Add a representer for the given type. + Multi-representer is a function accepting a Dumper instance + and an instance of the given data type or subtype + and producing the corresponding representation node. + """ + Dumper.add_multi_representer(data_type, multi_representer) + +class YAMLObjectMetaclass(type): + """ + The metaclass for YAMLObject. + """ + def __init__(cls, name, bases, kwds): + super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds) + if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None: + if isinstance(cls.yaml_loader, list): + for loader in cls.yaml_loader: + loader.add_constructor(cls.yaml_tag, cls.from_yaml) + else: + cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml) + + cls.yaml_dumper.add_representer(cls, cls.to_yaml) + +class YAMLObject(metaclass=YAMLObjectMetaclass): + """ + An object that can dump itself to a YAML stream + and load itself from a YAML stream. + """ + + __slots__ = () # no direct instantiation, so allow immutable subclasses + + yaml_loader = [Loader, FullLoader, UnsafeLoader] + yaml_dumper = Dumper + + yaml_tag = None + yaml_flow_style = None + + @classmethod + def from_yaml(cls, loader, node): + """ + Convert a representation node to a Python object. + """ + return loader.construct_yaml_object(node, cls) + + @classmethod + def to_yaml(cls, dumper, data): + """ + Convert a Python object to a representation node. + """ + return dumper.represent_yaml_object(cls.yaml_tag, data, cls, + flow_style=cls.yaml_flow_style) + diff --git a/robot/lib/python3.8/site-packages/yaml/composer.py b/robot/lib/python3.8/site-packages/yaml/composer.py new file mode 100644 index 0000000000000000000000000000000000000000..6d15cb40e3b4198819c91c6f8d8b32807fcf53b2 --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/composer.py @@ -0,0 +1,139 @@ + +__all__ = ['Composer', 'ComposerError'] + +from .error import MarkedYAMLError +from .events import * +from .nodes import * + +class ComposerError(MarkedYAMLError): + pass + +class Composer: + + def __init__(self): + self.anchors = {} + + def check_node(self): + # Drop the STREAM-START event. + if self.check_event(StreamStartEvent): + self.get_event() + + # If there are more documents available? + return not self.check_event(StreamEndEvent) + + def get_node(self): + # Get the root node of the next document. + if not self.check_event(StreamEndEvent): + return self.compose_document() + + def get_single_node(self): + # Drop the STREAM-START event. + self.get_event() + + # Compose a document if the stream is not empty. + document = None + if not self.check_event(StreamEndEvent): + document = self.compose_document() + + # Ensure that the stream contains no more documents. + if not self.check_event(StreamEndEvent): + event = self.get_event() + raise ComposerError("expected a single document in the stream", + document.start_mark, "but found another document", + event.start_mark) + + # Drop the STREAM-END event. + self.get_event() + + return document + + def compose_document(self): + # Drop the DOCUMENT-START event. + self.get_event() + + # Compose the root node. + node = self.compose_node(None, None) + + # Drop the DOCUMENT-END event. + self.get_event() + + self.anchors = {} + return node + + def compose_node(self, parent, index): + if self.check_event(AliasEvent): + event = self.get_event() + anchor = event.anchor + if anchor not in self.anchors: + raise ComposerError(None, None, "found undefined alias %r" + % anchor, event.start_mark) + return self.anchors[anchor] + event = self.peek_event() + anchor = event.anchor + if anchor is not None: + if anchor in self.anchors: + raise ComposerError("found duplicate anchor %r; first occurrence" + % anchor, self.anchors[anchor].start_mark, + "second occurrence", event.start_mark) + self.descend_resolver(parent, index) + if self.check_event(ScalarEvent): + node = self.compose_scalar_node(anchor) + elif self.check_event(SequenceStartEvent): + node = self.compose_sequence_node(anchor) + elif self.check_event(MappingStartEvent): + node = self.compose_mapping_node(anchor) + self.ascend_resolver() + return node + + def compose_scalar_node(self, anchor): + event = self.get_event() + tag = event.tag + if tag is None or tag == '!': + tag = self.resolve(ScalarNode, event.value, event.implicit) + node = ScalarNode(tag, event.value, + event.start_mark, event.end_mark, style=event.style) + if anchor is not None: + self.anchors[anchor] = node + return node + + def compose_sequence_node(self, anchor): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == '!': + tag = self.resolve(SequenceNode, None, start_event.implicit) + node = SequenceNode(tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style) + if anchor is not None: + self.anchors[anchor] = node + index = 0 + while not self.check_event(SequenceEndEvent): + node.value.append(self.compose_node(node, index)) + index += 1 + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + + def compose_mapping_node(self, anchor): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == '!': + tag = self.resolve(MappingNode, None, start_event.implicit) + node = MappingNode(tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style) + if anchor is not None: + self.anchors[anchor] = node + while not self.check_event(MappingEndEvent): + #key_event = self.peek_event() + item_key = self.compose_node(node, None) + #if item_key in node.value: + # raise ComposerError("while composing a mapping", start_event.start_mark, + # "found duplicate key", key_event.start_mark) + item_value = self.compose_node(node, item_key) + #node.value[item_key] = item_value + node.value.append((item_key, item_value)) + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + diff --git a/robot/lib/python3.8/site-packages/yaml/constructor.py b/robot/lib/python3.8/site-packages/yaml/constructor.py new file mode 100644 index 0000000000000000000000000000000000000000..1948b125c205261864156b18178c1af3f535ad76 --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/constructor.py @@ -0,0 +1,748 @@ + +__all__ = [ + 'BaseConstructor', + 'SafeConstructor', + 'FullConstructor', + 'UnsafeConstructor', + 'Constructor', + 'ConstructorError' +] + +from .error import * +from .nodes import * + +import collections.abc, datetime, base64, binascii, re, sys, types + +class ConstructorError(MarkedYAMLError): + pass + +class BaseConstructor: + + yaml_constructors = {} + yaml_multi_constructors = {} + + def __init__(self): + self.constructed_objects = {} + self.recursive_objects = {} + self.state_generators = [] + self.deep_construct = False + + def check_data(self): + # If there are more documents available? + return self.check_node() + + def check_state_key(self, key): + """Block special attributes/methods from being set in a newly created + object, to prevent user-controlled methods from being called during + deserialization""" + if self.get_state_keys_blacklist_regexp().match(key): + raise ConstructorError(None, None, + "blacklisted key '%s' in instance state found" % (key,), None) + + def get_data(self): + # Construct and return the next document. + if self.check_node(): + return self.construct_document(self.get_node()) + + def get_single_data(self): + # Ensure that the stream contains a single document and construct it. + node = self.get_single_node() + if node is not None: + return self.construct_document(node) + return None + + def construct_document(self, node): + data = self.construct_object(node) + while self.state_generators: + state_generators = self.state_generators + self.state_generators = [] + for generator in state_generators: + for dummy in generator: + pass + self.constructed_objects = {} + self.recursive_objects = {} + self.deep_construct = False + return data + + def construct_object(self, node, deep=False): + if node in self.constructed_objects: + return self.constructed_objects[node] + if deep: + old_deep = self.deep_construct + self.deep_construct = True + if node in self.recursive_objects: + raise ConstructorError(None, None, + "found unconstructable recursive node", node.start_mark) + self.recursive_objects[node] = None + constructor = None + tag_suffix = None + if node.tag in self.yaml_constructors: + constructor = self.yaml_constructors[node.tag] + else: + for tag_prefix in self.yaml_multi_constructors: + if tag_prefix is not None and node.tag.startswith(tag_prefix): + tag_suffix = node.tag[len(tag_prefix):] + constructor = self.yaml_multi_constructors[tag_prefix] + break + else: + if None in self.yaml_multi_constructors: + tag_suffix = node.tag + constructor = self.yaml_multi_constructors[None] + elif None in self.yaml_constructors: + constructor = self.yaml_constructors[None] + elif isinstance(node, ScalarNode): + constructor = self.__class__.construct_scalar + elif isinstance(node, SequenceNode): + constructor = self.__class__.construct_sequence + elif isinstance(node, MappingNode): + constructor = self.__class__.construct_mapping + if tag_suffix is None: + data = constructor(self, node) + else: + data = constructor(self, tag_suffix, node) + if isinstance(data, types.GeneratorType): + generator = data + data = next(generator) + if self.deep_construct: + for dummy in generator: + pass + else: + self.state_generators.append(generator) + self.constructed_objects[node] = data + del self.recursive_objects[node] + if deep: + self.deep_construct = old_deep + return data + + def construct_scalar(self, node): + if not isinstance(node, ScalarNode): + raise ConstructorError(None, None, + "expected a scalar node, but found %s" % node.id, + node.start_mark) + return node.value + + def construct_sequence(self, node, deep=False): + if not isinstance(node, SequenceNode): + raise ConstructorError(None, None, + "expected a sequence node, but found %s" % node.id, + node.start_mark) + return [self.construct_object(child, deep=deep) + for child in node.value] + + def construct_mapping(self, node, deep=False): + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + mapping = {} + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + if not isinstance(key, collections.abc.Hashable): + raise ConstructorError("while constructing a mapping", node.start_mark, + "found unhashable key", key_node.start_mark) + value = self.construct_object(value_node, deep=deep) + mapping[key] = value + return mapping + + def construct_pairs(self, node, deep=False): + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + pairs = [] + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + value = self.construct_object(value_node, deep=deep) + pairs.append((key, value)) + return pairs + + @classmethod + def add_constructor(cls, tag, constructor): + if not 'yaml_constructors' in cls.__dict__: + cls.yaml_constructors = cls.yaml_constructors.copy() + cls.yaml_constructors[tag] = constructor + + @classmethod + def add_multi_constructor(cls, tag_prefix, multi_constructor): + if not 'yaml_multi_constructors' in cls.__dict__: + cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy() + cls.yaml_multi_constructors[tag_prefix] = multi_constructor + +class SafeConstructor(BaseConstructor): + + def construct_scalar(self, node): + if isinstance(node, MappingNode): + for key_node, value_node in node.value: + if key_node.tag == 'tag:yaml.org,2002:value': + return self.construct_scalar(value_node) + return super().construct_scalar(node) + + def flatten_mapping(self, node): + merge = [] + index = 0 + while index < len(node.value): + key_node, value_node = node.value[index] + if key_node.tag == 'tag:yaml.org,2002:merge': + del node.value[index] + if isinstance(value_node, MappingNode): + self.flatten_mapping(value_node) + merge.extend(value_node.value) + elif isinstance(value_node, SequenceNode): + submerge = [] + for subnode in value_node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing a mapping", + node.start_mark, + "expected a mapping for merging, but found %s" + % subnode.id, subnode.start_mark) + self.flatten_mapping(subnode) + submerge.append(subnode.value) + submerge.reverse() + for value in submerge: + merge.extend(value) + else: + raise ConstructorError("while constructing a mapping", node.start_mark, + "expected a mapping or list of mappings for merging, but found %s" + % value_node.id, value_node.start_mark) + elif key_node.tag == 'tag:yaml.org,2002:value': + key_node.tag = 'tag:yaml.org,2002:str' + index += 1 + else: + index += 1 + if merge: + node.value = merge + node.value + + def construct_mapping(self, node, deep=False): + if isinstance(node, MappingNode): + self.flatten_mapping(node) + return super().construct_mapping(node, deep=deep) + + def construct_yaml_null(self, node): + self.construct_scalar(node) + return None + + bool_values = { + 'yes': True, + 'no': False, + 'true': True, + 'false': False, + 'on': True, + 'off': False, + } + + def construct_yaml_bool(self, node): + value = self.construct_scalar(node) + return self.bool_values[value.lower()] + + def construct_yaml_int(self, node): + value = self.construct_scalar(node) + value = value.replace('_', '') + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '0': + return 0 + elif value.startswith('0b'): + return sign*int(value[2:], 2) + elif value.startswith('0x'): + return sign*int(value[2:], 16) + elif value[0] == '0': + return sign*int(value, 8) + elif ':' in value: + digits = [int(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*int(value) + + inf_value = 1e300 + while inf_value != inf_value*inf_value: + inf_value *= inf_value + nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99). + + def construct_yaml_float(self, node): + value = self.construct_scalar(node) + value = value.replace('_', '').lower() + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '.inf': + return sign*self.inf_value + elif value == '.nan': + return self.nan_value + elif ':' in value: + digits = [float(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0.0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*float(value) + + def construct_yaml_binary(self, node): + try: + value = self.construct_scalar(node).encode('ascii') + except UnicodeEncodeError as exc: + raise ConstructorError(None, None, + "failed to convert base64 data into ascii: %s" % exc, + node.start_mark) + try: + if hasattr(base64, 'decodebytes'): + return base64.decodebytes(value) + else: + return base64.decodestring(value) + except binascii.Error as exc: + raise ConstructorError(None, None, + "failed to decode base64 data: %s" % exc, node.start_mark) + + timestamp_regexp = re.compile( + r'''^(?P[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)?$''', re.X) + + def construct_yaml_timestamp(self, node): + value = self.construct_scalar(node) + match = self.timestamp_regexp.match(node.value) + values = match.groupdict() + year = int(values['year']) + month = int(values['month']) + day = int(values['day']) + if not values['hour']: + return datetime.date(year, month, day) + hour = int(values['hour']) + minute = int(values['minute']) + second = int(values['second']) + fraction = 0 + tzinfo = None + if values['fraction']: + fraction = values['fraction'][:6] + while len(fraction) < 6: + fraction += '0' + fraction = int(fraction) + if values['tz_sign']: + tz_hour = int(values['tz_hour']) + tz_minute = int(values['tz_minute'] or 0) + delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute) + if values['tz_sign'] == '-': + delta = -delta + tzinfo = datetime.timezone(delta) + elif values['tz']: + tzinfo = datetime.timezone.utc + return datetime.datetime(year, month, day, hour, minute, second, fraction, + tzinfo=tzinfo) + + def construct_yaml_omap(self, node): + # Note: we do not check for duplicate keys, because it's too + # CPU-expensive. + omap = [] + yield omap + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark) + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark) + if len(subnode.value) != 1: + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark) + key_node, value_node = subnode.value[0] + key = self.construct_object(key_node) + value = self.construct_object(value_node) + omap.append((key, value)) + + def construct_yaml_pairs(self, node): + # Note: the same code as `construct_yaml_omap`. + pairs = [] + yield pairs + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark) + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark) + if len(subnode.value) != 1: + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark) + key_node, value_node = subnode.value[0] + key = self.construct_object(key_node) + value = self.construct_object(value_node) + pairs.append((key, value)) + + def construct_yaml_set(self, node): + data = set() + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_yaml_str(self, node): + return self.construct_scalar(node) + + def construct_yaml_seq(self, node): + data = [] + yield data + data.extend(self.construct_sequence(node)) + + def construct_yaml_map(self, node): + data = {} + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_yaml_object(self, node, cls): + data = cls.__new__(cls) + yield data + if hasattr(data, '__setstate__'): + state = self.construct_mapping(node, deep=True) + data.__setstate__(state) + else: + state = self.construct_mapping(node) + data.__dict__.update(state) + + def construct_undefined(self, node): + raise ConstructorError(None, None, + "could not determine a constructor for the tag %r" % node.tag, + node.start_mark) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:null', + SafeConstructor.construct_yaml_null) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:bool', + SafeConstructor.construct_yaml_bool) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:int', + SafeConstructor.construct_yaml_int) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:float', + SafeConstructor.construct_yaml_float) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:binary', + SafeConstructor.construct_yaml_binary) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:timestamp', + SafeConstructor.construct_yaml_timestamp) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:omap', + SafeConstructor.construct_yaml_omap) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:pairs', + SafeConstructor.construct_yaml_pairs) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:set', + SafeConstructor.construct_yaml_set) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:str', + SafeConstructor.construct_yaml_str) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:seq', + SafeConstructor.construct_yaml_seq) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:map', + SafeConstructor.construct_yaml_map) + +SafeConstructor.add_constructor(None, + SafeConstructor.construct_undefined) + +class FullConstructor(SafeConstructor): + # 'extend' is blacklisted because it is used by + # construct_python_object_apply to add `listitems` to a newly generate + # python instance + def get_state_keys_blacklist(self): + return ['^extend$', '^__.*__$'] + + def get_state_keys_blacklist_regexp(self): + if not hasattr(self, 'state_keys_blacklist_regexp'): + self.state_keys_blacklist_regexp = re.compile('(' + '|'.join(self.get_state_keys_blacklist()) + ')') + return self.state_keys_blacklist_regexp + + def construct_python_str(self, node): + return self.construct_scalar(node) + + def construct_python_unicode(self, node): + return self.construct_scalar(node) + + def construct_python_bytes(self, node): + try: + value = self.construct_scalar(node).encode('ascii') + except UnicodeEncodeError as exc: + raise ConstructorError(None, None, + "failed to convert base64 data into ascii: %s" % exc, + node.start_mark) + try: + if hasattr(base64, 'decodebytes'): + return base64.decodebytes(value) + else: + return base64.decodestring(value) + except binascii.Error as exc: + raise ConstructorError(None, None, + "failed to decode base64 data: %s" % exc, node.start_mark) + + def construct_python_long(self, node): + return self.construct_yaml_int(node) + + def construct_python_complex(self, node): + return complex(self.construct_scalar(node)) + + def construct_python_tuple(self, node): + return tuple(self.construct_sequence(node)) + + def find_python_module(self, name, mark, unsafe=False): + if not name: + raise ConstructorError("while constructing a Python module", mark, + "expected non-empty name appended to the tag", mark) + if unsafe: + try: + __import__(name) + except ImportError as exc: + raise ConstructorError("while constructing a Python module", mark, + "cannot find module %r (%s)" % (name, exc), mark) + if name not in sys.modules: + raise ConstructorError("while constructing a Python module", mark, + "module %r is not imported" % name, mark) + return sys.modules[name] + + def find_python_name(self, name, mark, unsafe=False): + if not name: + raise ConstructorError("while constructing a Python object", mark, + "expected non-empty name appended to the tag", mark) + if '.' in name: + module_name, object_name = name.rsplit('.', 1) + else: + module_name = 'builtins' + object_name = name + if unsafe: + try: + __import__(module_name) + except ImportError as exc: + raise ConstructorError("while constructing a Python object", mark, + "cannot find module %r (%s)" % (module_name, exc), mark) + if module_name not in sys.modules: + raise ConstructorError("while constructing a Python object", mark, + "module %r is not imported" % module_name, mark) + module = sys.modules[module_name] + if not hasattr(module, object_name): + raise ConstructorError("while constructing a Python object", mark, + "cannot find %r in the module %r" + % (object_name, module.__name__), mark) + return getattr(module, object_name) + + def construct_python_name(self, suffix, node): + value = self.construct_scalar(node) + if value: + raise ConstructorError("while constructing a Python name", node.start_mark, + "expected the empty value, but found %r" % value, node.start_mark) + return self.find_python_name(suffix, node.start_mark) + + def construct_python_module(self, suffix, node): + value = self.construct_scalar(node) + if value: + raise ConstructorError("while constructing a Python module", node.start_mark, + "expected the empty value, but found %r" % value, node.start_mark) + return self.find_python_module(suffix, node.start_mark) + + def make_python_instance(self, suffix, node, + args=None, kwds=None, newobj=False, unsafe=False): + if not args: + args = [] + if not kwds: + kwds = {} + cls = self.find_python_name(suffix, node.start_mark) + if not (unsafe or isinstance(cls, type)): + raise ConstructorError("while constructing a Python instance", node.start_mark, + "expected a class, but found %r" % type(cls), + node.start_mark) + if newobj and isinstance(cls, type): + return cls.__new__(cls, *args, **kwds) + else: + return cls(*args, **kwds) + + def set_python_instance_state(self, instance, state, unsafe=False): + if hasattr(instance, '__setstate__'): + instance.__setstate__(state) + else: + slotstate = {} + if isinstance(state, tuple) and len(state) == 2: + state, slotstate = state + if hasattr(instance, '__dict__'): + if not unsafe and state: + for key in state.keys(): + self.check_state_key(key) + instance.__dict__.update(state) + elif state: + slotstate.update(state) + for key, value in slotstate.items(): + if not unsafe: + self.check_state_key(key) + setattr(instance, key, value) + + def construct_python_object(self, suffix, node): + # Format: + # !!python/object:module.name { ... state ... } + instance = self.make_python_instance(suffix, node, newobj=True) + yield instance + deep = hasattr(instance, '__setstate__') + state = self.construct_mapping(node, deep=deep) + self.set_python_instance_state(instance, state) + + def construct_python_object_apply(self, suffix, node, newobj=False): + # Format: + # !!python/object/apply # (or !!python/object/new) + # args: [ ... arguments ... ] + # kwds: { ... keywords ... } + # state: ... state ... + # listitems: [ ... listitems ... ] + # dictitems: { ... dictitems ... } + # or short format: + # !!python/object/apply [ ... arguments ... ] + # The difference between !!python/object/apply and !!python/object/new + # is how an object is created, check make_python_instance for details. + if isinstance(node, SequenceNode): + args = self.construct_sequence(node, deep=True) + kwds = {} + state = {} + listitems = [] + dictitems = {} + else: + value = self.construct_mapping(node, deep=True) + args = value.get('args', []) + kwds = value.get('kwds', {}) + state = value.get('state', {}) + listitems = value.get('listitems', []) + dictitems = value.get('dictitems', {}) + instance = self.make_python_instance(suffix, node, args, kwds, newobj) + if state: + self.set_python_instance_state(instance, state) + if listitems: + instance.extend(listitems) + if dictitems: + for key in dictitems: + instance[key] = dictitems[key] + return instance + + def construct_python_object_new(self, suffix, node): + return self.construct_python_object_apply(suffix, node, newobj=True) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/none', + FullConstructor.construct_yaml_null) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/bool', + FullConstructor.construct_yaml_bool) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/str', + FullConstructor.construct_python_str) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/unicode', + FullConstructor.construct_python_unicode) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/bytes', + FullConstructor.construct_python_bytes) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/int', + FullConstructor.construct_yaml_int) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/long', + FullConstructor.construct_python_long) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/float', + FullConstructor.construct_yaml_float) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/complex', + FullConstructor.construct_python_complex) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/list', + FullConstructor.construct_yaml_seq) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/tuple', + FullConstructor.construct_python_tuple) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/dict', + FullConstructor.construct_yaml_map) + +FullConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/name:', + FullConstructor.construct_python_name) + +FullConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/module:', + FullConstructor.construct_python_module) + +FullConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object:', + FullConstructor.construct_python_object) + +FullConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object/new:', + FullConstructor.construct_python_object_new) + +class UnsafeConstructor(FullConstructor): + + def find_python_module(self, name, mark): + return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True) + + def find_python_name(self, name, mark): + return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True) + + def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False): + return super(UnsafeConstructor, self).make_python_instance( + suffix, node, args, kwds, newobj, unsafe=True) + + def set_python_instance_state(self, instance, state): + return super(UnsafeConstructor, self).set_python_instance_state( + instance, state, unsafe=True) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object/apply:', + UnsafeConstructor.construct_python_object_apply) + +# Constructor is same as UnsafeConstructor. Need to leave this in place in case +# people have extended it directly. +class Constructor(UnsafeConstructor): + pass diff --git a/robot/lib/python3.8/site-packages/yaml/cyaml.py b/robot/lib/python3.8/site-packages/yaml/cyaml.py new file mode 100644 index 0000000000000000000000000000000000000000..1e606c74b9470c3ea0db4bd66629af45d28c2b9a --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/cyaml.py @@ -0,0 +1,101 @@ + +__all__ = [ + 'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader', + 'CBaseDumper', 'CSafeDumper', 'CDumper' +] + +from _yaml import CParser, CEmitter + +from .constructor import * + +from .serializer import * +from .representer import * + +from .resolver import * + +class CBaseLoader(CParser, BaseConstructor, BaseResolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + BaseConstructor.__init__(self) + BaseResolver.__init__(self) + +class CSafeLoader(CParser, SafeConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + SafeConstructor.__init__(self) + Resolver.__init__(self) + +class CFullLoader(CParser, FullConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + FullConstructor.__init__(self) + Resolver.__init__(self) + +class CUnsafeLoader(CParser, UnsafeConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + UnsafeConstructor.__init__(self) + Resolver.__init__(self) + +class CLoader(CParser, Constructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + Constructor.__init__(self) + Resolver.__init__(self) + +class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class CSafeDumper(CEmitter, SafeRepresenter, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + SafeRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class CDumper(CEmitter, Serializer, Representer, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + diff --git a/robot/lib/python3.8/site-packages/yaml/dumper.py b/robot/lib/python3.8/site-packages/yaml/dumper.py new file mode 100644 index 0000000000000000000000000000000000000000..6aadba551f3836b02f4752277f4b3027073defad --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/dumper.py @@ -0,0 +1,62 @@ + +__all__ = ['BaseDumper', 'SafeDumper', 'Dumper'] + +from .emitter import * +from .serializer import * +from .representer import * +from .resolver import * + +class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + SafeRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class Dumper(Emitter, Serializer, Representer, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + diff --git a/robot/lib/python3.8/site-packages/yaml/emitter.py b/robot/lib/python3.8/site-packages/yaml/emitter.py new file mode 100644 index 0000000000000000000000000000000000000000..a664d011162af69184df2f8e59ab7feec818f7c7 --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/emitter.py @@ -0,0 +1,1137 @@ + +# Emitter expects events obeying the following grammar: +# stream ::= STREAM-START document* STREAM-END +# document ::= DOCUMENT-START node DOCUMENT-END +# node ::= SCALAR | sequence | mapping +# sequence ::= SEQUENCE-START node* SEQUENCE-END +# mapping ::= MAPPING-START (node node)* MAPPING-END + +__all__ = ['Emitter', 'EmitterError'] + +from .error import YAMLError +from .events import * + +class EmitterError(YAMLError): + pass + +class ScalarAnalysis: + def __init__(self, scalar, empty, multiline, + allow_flow_plain, allow_block_plain, + allow_single_quoted, allow_double_quoted, + allow_block): + self.scalar = scalar + self.empty = empty + self.multiline = multiline + self.allow_flow_plain = allow_flow_plain + self.allow_block_plain = allow_block_plain + self.allow_single_quoted = allow_single_quoted + self.allow_double_quoted = allow_double_quoted + self.allow_block = allow_block + +class Emitter: + + DEFAULT_TAG_PREFIXES = { + '!' : '!', + 'tag:yaml.org,2002:' : '!!', + } + + def __init__(self, stream, canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None): + + # The stream should have the methods `write` and possibly `flush`. + self.stream = stream + + # Encoding can be overridden by STREAM-START. + self.encoding = None + + # Emitter is a state machine with a stack of states to handle nested + # structures. + self.states = [] + self.state = self.expect_stream_start + + # Current event and the event queue. + self.events = [] + self.event = None + + # The current indentation level and the stack of previous indents. + self.indents = [] + self.indent = None + + # Flow level. + self.flow_level = 0 + + # Contexts. + self.root_context = False + self.sequence_context = False + self.mapping_context = False + self.simple_key_context = False + + # Characteristics of the last emitted character: + # - current position. + # - is it a whitespace? + # - is it an indention character + # (indentation space, '-', '?', or ':')? + self.line = 0 + self.column = 0 + self.whitespace = True + self.indention = True + + # Whether the document requires an explicit document indicator + self.open_ended = False + + # Formatting details. + self.canonical = canonical + self.allow_unicode = allow_unicode + self.best_indent = 2 + if indent and 1 < indent < 10: + self.best_indent = indent + self.best_width = 80 + if width and width > self.best_indent*2: + self.best_width = width + self.best_line_break = '\n' + if line_break in ['\r', '\n', '\r\n']: + self.best_line_break = line_break + + # Tag prefixes. + self.tag_prefixes = None + + # Prepared anchor and tag. + self.prepared_anchor = None + self.prepared_tag = None + + # Scalar analysis and style. + self.analysis = None + self.style = None + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def emit(self, event): + self.events.append(event) + while not self.need_more_events(): + self.event = self.events.pop(0) + self.state() + self.event = None + + # In some cases, we wait for a few next events before emitting. + + def need_more_events(self): + if not self.events: + return True + event = self.events[0] + if isinstance(event, DocumentStartEvent): + return self.need_events(1) + elif isinstance(event, SequenceStartEvent): + return self.need_events(2) + elif isinstance(event, MappingStartEvent): + return self.need_events(3) + else: + return False + + def need_events(self, count): + level = 0 + for event in self.events[1:]: + if isinstance(event, (DocumentStartEvent, CollectionStartEvent)): + level += 1 + elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)): + level -= 1 + elif isinstance(event, StreamEndEvent): + level = -1 + if level < 0: + return False + return (len(self.events) < count+1) + + def increase_indent(self, flow=False, indentless=False): + self.indents.append(self.indent) + if self.indent is None: + if flow: + self.indent = self.best_indent + else: + self.indent = 0 + elif not indentless: + self.indent += self.best_indent + + # States. + + # Stream handlers. + + def expect_stream_start(self): + if isinstance(self.event, StreamStartEvent): + if self.event.encoding and not hasattr(self.stream, 'encoding'): + self.encoding = self.event.encoding + self.write_stream_start() + self.state = self.expect_first_document_start + else: + raise EmitterError("expected StreamStartEvent, but got %s" + % self.event) + + def expect_nothing(self): + raise EmitterError("expected nothing, but got %s" % self.event) + + # Document handlers. + + def expect_first_document_start(self): + return self.expect_document_start(first=True) + + def expect_document_start(self, first=False): + if isinstance(self.event, DocumentStartEvent): + if (self.event.version or self.event.tags) and self.open_ended: + self.write_indicator('...', True) + self.write_indent() + if self.event.version: + version_text = self.prepare_version(self.event.version) + self.write_version_directive(version_text) + self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy() + if self.event.tags: + handles = sorted(self.event.tags.keys()) + for handle in handles: + prefix = self.event.tags[handle] + self.tag_prefixes[prefix] = handle + handle_text = self.prepare_tag_handle(handle) + prefix_text = self.prepare_tag_prefix(prefix) + self.write_tag_directive(handle_text, prefix_text) + implicit = (first and not self.event.explicit and not self.canonical + and not self.event.version and not self.event.tags + and not self.check_empty_document()) + if not implicit: + self.write_indent() + self.write_indicator('---', True) + if self.canonical: + self.write_indent() + self.state = self.expect_document_root + elif isinstance(self.event, StreamEndEvent): + if self.open_ended: + self.write_indicator('...', True) + self.write_indent() + self.write_stream_end() + self.state = self.expect_nothing + else: + raise EmitterError("expected DocumentStartEvent, but got %s" + % self.event) + + def expect_document_end(self): + if isinstance(self.event, DocumentEndEvent): + self.write_indent() + if self.event.explicit: + self.write_indicator('...', True) + self.write_indent() + self.flush_stream() + self.state = self.expect_document_start + else: + raise EmitterError("expected DocumentEndEvent, but got %s" + % self.event) + + def expect_document_root(self): + self.states.append(self.expect_document_end) + self.expect_node(root=True) + + # Node handlers. + + def expect_node(self, root=False, sequence=False, mapping=False, + simple_key=False): + self.root_context = root + self.sequence_context = sequence + self.mapping_context = mapping + self.simple_key_context = simple_key + if isinstance(self.event, AliasEvent): + self.expect_alias() + elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)): + self.process_anchor('&') + self.process_tag() + if isinstance(self.event, ScalarEvent): + self.expect_scalar() + elif isinstance(self.event, SequenceStartEvent): + if self.flow_level or self.canonical or self.event.flow_style \ + or self.check_empty_sequence(): + self.expect_flow_sequence() + else: + self.expect_block_sequence() + elif isinstance(self.event, MappingStartEvent): + if self.flow_level or self.canonical or self.event.flow_style \ + or self.check_empty_mapping(): + self.expect_flow_mapping() + else: + self.expect_block_mapping() + else: + raise EmitterError("expected NodeEvent, but got %s" % self.event) + + def expect_alias(self): + if self.event.anchor is None: + raise EmitterError("anchor is not specified for alias") + self.process_anchor('*') + self.state = self.states.pop() + + def expect_scalar(self): + self.increase_indent(flow=True) + self.process_scalar() + self.indent = self.indents.pop() + self.state = self.states.pop() + + # Flow sequence handlers. + + def expect_flow_sequence(self): + self.write_indicator('[', True, whitespace=True) + self.flow_level += 1 + self.increase_indent(flow=True) + self.state = self.expect_first_flow_sequence_item + + def expect_first_flow_sequence_item(self): + if isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + self.write_indicator(']', False) + self.state = self.states.pop() + else: + if self.canonical or self.column > self.best_width: + self.write_indent() + self.states.append(self.expect_flow_sequence_item) + self.expect_node(sequence=True) + + def expect_flow_sequence_item(self): + if isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + if self.canonical: + self.write_indicator(',', False) + self.write_indent() + self.write_indicator(']', False) + self.state = self.states.pop() + else: + self.write_indicator(',', False) + if self.canonical or self.column > self.best_width: + self.write_indent() + self.states.append(self.expect_flow_sequence_item) + self.expect_node(sequence=True) + + # Flow mapping handlers. + + def expect_flow_mapping(self): + self.write_indicator('{', True, whitespace=True) + self.flow_level += 1 + self.increase_indent(flow=True) + self.state = self.expect_first_flow_mapping_key + + def expect_first_flow_mapping_key(self): + if isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + self.write_indicator('}', False) + self.state = self.states.pop() + else: + if self.canonical or self.column > self.best_width: + self.write_indent() + if not self.canonical and self.check_simple_key(): + self.states.append(self.expect_flow_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True) + self.states.append(self.expect_flow_mapping_value) + self.expect_node(mapping=True) + + def expect_flow_mapping_key(self): + if isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + if self.canonical: + self.write_indicator(',', False) + self.write_indent() + self.write_indicator('}', False) + self.state = self.states.pop() + else: + self.write_indicator(',', False) + if self.canonical or self.column > self.best_width: + self.write_indent() + if not self.canonical and self.check_simple_key(): + self.states.append(self.expect_flow_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True) + self.states.append(self.expect_flow_mapping_value) + self.expect_node(mapping=True) + + def expect_flow_mapping_simple_value(self): + self.write_indicator(':', False) + self.states.append(self.expect_flow_mapping_key) + self.expect_node(mapping=True) + + def expect_flow_mapping_value(self): + if self.canonical or self.column > self.best_width: + self.write_indent() + self.write_indicator(':', True) + self.states.append(self.expect_flow_mapping_key) + self.expect_node(mapping=True) + + # Block sequence handlers. + + def expect_block_sequence(self): + indentless = (self.mapping_context and not self.indention) + self.increase_indent(flow=False, indentless=indentless) + self.state = self.expect_first_block_sequence_item + + def expect_first_block_sequence_item(self): + return self.expect_block_sequence_item(first=True) + + def expect_block_sequence_item(self, first=False): + if not first and isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.state = self.states.pop() + else: + self.write_indent() + self.write_indicator('-', True, indention=True) + self.states.append(self.expect_block_sequence_item) + self.expect_node(sequence=True) + + # Block mapping handlers. + + def expect_block_mapping(self): + self.increase_indent(flow=False) + self.state = self.expect_first_block_mapping_key + + def expect_first_block_mapping_key(self): + return self.expect_block_mapping_key(first=True) + + def expect_block_mapping_key(self, first=False): + if not first and isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.state = self.states.pop() + else: + self.write_indent() + if self.check_simple_key(): + self.states.append(self.expect_block_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True, indention=True) + self.states.append(self.expect_block_mapping_value) + self.expect_node(mapping=True) + + def expect_block_mapping_simple_value(self): + self.write_indicator(':', False) + self.states.append(self.expect_block_mapping_key) + self.expect_node(mapping=True) + + def expect_block_mapping_value(self): + self.write_indent() + self.write_indicator(':', True, indention=True) + self.states.append(self.expect_block_mapping_key) + self.expect_node(mapping=True) + + # Checkers. + + def check_empty_sequence(self): + return (isinstance(self.event, SequenceStartEvent) and self.events + and isinstance(self.events[0], SequenceEndEvent)) + + def check_empty_mapping(self): + return (isinstance(self.event, MappingStartEvent) and self.events + and isinstance(self.events[0], MappingEndEvent)) + + def check_empty_document(self): + if not isinstance(self.event, DocumentStartEvent) or not self.events: + return False + event = self.events[0] + return (isinstance(event, ScalarEvent) and event.anchor is None + and event.tag is None and event.implicit and event.value == '') + + def check_simple_key(self): + length = 0 + if isinstance(self.event, NodeEvent) and self.event.anchor is not None: + if self.prepared_anchor is None: + self.prepared_anchor = self.prepare_anchor(self.event.anchor) + length += len(self.prepared_anchor) + if isinstance(self.event, (ScalarEvent, CollectionStartEvent)) \ + and self.event.tag is not None: + if self.prepared_tag is None: + self.prepared_tag = self.prepare_tag(self.event.tag) + length += len(self.prepared_tag) + if isinstance(self.event, ScalarEvent): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + length += len(self.analysis.scalar) + return (length < 128 and (isinstance(self.event, AliasEvent) + or (isinstance(self.event, ScalarEvent) + and not self.analysis.empty and not self.analysis.multiline) + or self.check_empty_sequence() or self.check_empty_mapping())) + + # Anchor, Tag, and Scalar processors. + + def process_anchor(self, indicator): + if self.event.anchor is None: + self.prepared_anchor = None + return + if self.prepared_anchor is None: + self.prepared_anchor = self.prepare_anchor(self.event.anchor) + if self.prepared_anchor: + self.write_indicator(indicator+self.prepared_anchor, True) + self.prepared_anchor = None + + def process_tag(self): + tag = self.event.tag + if isinstance(self.event, ScalarEvent): + if self.style is None: + self.style = self.choose_scalar_style() + if ((not self.canonical or tag is None) and + ((self.style == '' and self.event.implicit[0]) + or (self.style != '' and self.event.implicit[1]))): + self.prepared_tag = None + return + if self.event.implicit[0] and tag is None: + tag = '!' + self.prepared_tag = None + else: + if (not self.canonical or tag is None) and self.event.implicit: + self.prepared_tag = None + return + if tag is None: + raise EmitterError("tag is not specified") + if self.prepared_tag is None: + self.prepared_tag = self.prepare_tag(tag) + if self.prepared_tag: + self.write_indicator(self.prepared_tag, True) + self.prepared_tag = None + + def choose_scalar_style(self): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + if self.event.style == '"' or self.canonical: + return '"' + if not self.event.style and self.event.implicit[0]: + if (not (self.simple_key_context and + (self.analysis.empty or self.analysis.multiline)) + and (self.flow_level and self.analysis.allow_flow_plain + or (not self.flow_level and self.analysis.allow_block_plain))): + return '' + if self.event.style and self.event.style in '|>': + if (not self.flow_level and not self.simple_key_context + and self.analysis.allow_block): + return self.event.style + if not self.event.style or self.event.style == '\'': + if (self.analysis.allow_single_quoted and + not (self.simple_key_context and self.analysis.multiline)): + return '\'' + return '"' + + def process_scalar(self): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + if self.style is None: + self.style = self.choose_scalar_style() + split = (not self.simple_key_context) + #if self.analysis.multiline and split \ + # and (not self.style or self.style in '\'\"'): + # self.write_indent() + if self.style == '"': + self.write_double_quoted(self.analysis.scalar, split) + elif self.style == '\'': + self.write_single_quoted(self.analysis.scalar, split) + elif self.style == '>': + self.write_folded(self.analysis.scalar) + elif self.style == '|': + self.write_literal(self.analysis.scalar) + else: + self.write_plain(self.analysis.scalar, split) + self.analysis = None + self.style = None + + # Analyzers. + + def prepare_version(self, version): + major, minor = version + if major != 1: + raise EmitterError("unsupported YAML version: %d.%d" % (major, minor)) + return '%d.%d' % (major, minor) + + def prepare_tag_handle(self, handle): + if not handle: + raise EmitterError("tag handle must not be empty") + if handle[0] != '!' or handle[-1] != '!': + raise EmitterError("tag handle must start and end with '!': %r" % handle) + for ch in handle[1:-1]: + if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_'): + raise EmitterError("invalid character %r in the tag handle: %r" + % (ch, handle)) + return handle + + def prepare_tag_prefix(self, prefix): + if not prefix: + raise EmitterError("tag prefix must not be empty") + chunks = [] + start = end = 0 + if prefix[0] == '!': + end = 1 + while end < len(prefix): + ch = prefix[end] + if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?!:@&=+$,_.~*\'()[]': + end += 1 + else: + if start < end: + chunks.append(prefix[start:end]) + start = end = end+1 + data = ch.encode('utf-8') + for ch in data: + chunks.append('%%%02X' % ord(ch)) + if start < end: + chunks.append(prefix[start:end]) + return ''.join(chunks) + + def prepare_tag(self, tag): + if not tag: + raise EmitterError("tag must not be empty") + if tag == '!': + return tag + handle = None + suffix = tag + prefixes = sorted(self.tag_prefixes.keys()) + for prefix in prefixes: + if tag.startswith(prefix) \ + and (prefix == '!' or len(prefix) < len(tag)): + handle = self.tag_prefixes[prefix] + suffix = tag[len(prefix):] + chunks = [] + start = end = 0 + while end < len(suffix): + ch = suffix[end] + if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?:@&=+$,_.~*\'()[]' \ + or (ch == '!' and handle != '!'): + end += 1 + else: + if start < end: + chunks.append(suffix[start:end]) + start = end = end+1 + data = ch.encode('utf-8') + for ch in data: + chunks.append('%%%02X' % ch) + if start < end: + chunks.append(suffix[start:end]) + suffix_text = ''.join(chunks) + if handle: + return '%s%s' % (handle, suffix_text) + else: + return '!<%s>' % suffix_text + + def prepare_anchor(self, anchor): + if not anchor: + raise EmitterError("anchor must not be empty") + for ch in anchor: + if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_'): + raise EmitterError("invalid character %r in the anchor: %r" + % (ch, anchor)) + return anchor + + def analyze_scalar(self, scalar): + + # Empty scalar is a special case. + if not scalar: + return ScalarAnalysis(scalar=scalar, empty=True, multiline=False, + allow_flow_plain=False, allow_block_plain=True, + allow_single_quoted=True, allow_double_quoted=True, + allow_block=False) + + # Indicators and special characters. + block_indicators = False + flow_indicators = False + line_breaks = False + special_characters = False + + # Important whitespace combinations. + leading_space = False + leading_break = False + trailing_space = False + trailing_break = False + break_space = False + space_break = False + + # Check document indicators. + if scalar.startswith('---') or scalar.startswith('...'): + block_indicators = True + flow_indicators = True + + # First character or preceded by a whitespace. + preceded_by_whitespace = True + + # Last character or followed by a whitespace. + followed_by_whitespace = (len(scalar) == 1 or + scalar[1] in '\0 \t\r\n\x85\u2028\u2029') + + # The previous character is a space. + previous_space = False + + # The previous character is a break. + previous_break = False + + index = 0 + while index < len(scalar): + ch = scalar[index] + + # Check for indicators. + if index == 0: + # Leading indicators are special characters. + if ch in '#,[]{}&*!|>\'\"%@`': + flow_indicators = True + block_indicators = True + if ch in '?:': + flow_indicators = True + if followed_by_whitespace: + block_indicators = True + if ch == '-' and followed_by_whitespace: + flow_indicators = True + block_indicators = True + else: + # Some indicators cannot appear within a scalar as well. + if ch in ',?[]{}': + flow_indicators = True + if ch == ':': + flow_indicators = True + if followed_by_whitespace: + block_indicators = True + if ch == '#' and preceded_by_whitespace: + flow_indicators = True + block_indicators = True + + # Check for line breaks, special, and unicode characters. + if ch in '\n\x85\u2028\u2029': + line_breaks = True + if not (ch == '\n' or '\x20' <= ch <= '\x7E'): + if (ch == '\x85' or '\xA0' <= ch <= '\uD7FF' + or '\uE000' <= ch <= '\uFFFD' + or '\U00010000' <= ch < '\U0010ffff') and ch != '\uFEFF': + unicode_characters = True + if not self.allow_unicode: + special_characters = True + else: + special_characters = True + + # Detect important whitespace combinations. + if ch == ' ': + if index == 0: + leading_space = True + if index == len(scalar)-1: + trailing_space = True + if previous_break: + break_space = True + previous_space = True + previous_break = False + elif ch in '\n\x85\u2028\u2029': + if index == 0: + leading_break = True + if index == len(scalar)-1: + trailing_break = True + if previous_space: + space_break = True + previous_space = False + previous_break = True + else: + previous_space = False + previous_break = False + + # Prepare for the next character. + index += 1 + preceded_by_whitespace = (ch in '\0 \t\r\n\x85\u2028\u2029') + followed_by_whitespace = (index+1 >= len(scalar) or + scalar[index+1] in '\0 \t\r\n\x85\u2028\u2029') + + # Let's decide what styles are allowed. + allow_flow_plain = True + allow_block_plain = True + allow_single_quoted = True + allow_double_quoted = True + allow_block = True + + # Leading and trailing whitespaces are bad for plain scalars. + if (leading_space or leading_break + or trailing_space or trailing_break): + allow_flow_plain = allow_block_plain = False + + # We do not permit trailing spaces for block scalars. + if trailing_space: + allow_block = False + + # Spaces at the beginning of a new line are only acceptable for block + # scalars. + if break_space: + allow_flow_plain = allow_block_plain = allow_single_quoted = False + + # Spaces followed by breaks, as well as special character are only + # allowed for double quoted scalars. + if space_break or special_characters: + allow_flow_plain = allow_block_plain = \ + allow_single_quoted = allow_block = False + + # Although the plain scalar writer supports breaks, we never emit + # multiline plain scalars. + if line_breaks: + allow_flow_plain = allow_block_plain = False + + # Flow indicators are forbidden for flow plain scalars. + if flow_indicators: + allow_flow_plain = False + + # Block indicators are forbidden for block plain scalars. + if block_indicators: + allow_block_plain = False + + return ScalarAnalysis(scalar=scalar, + empty=False, multiline=line_breaks, + allow_flow_plain=allow_flow_plain, + allow_block_plain=allow_block_plain, + allow_single_quoted=allow_single_quoted, + allow_double_quoted=allow_double_quoted, + allow_block=allow_block) + + # Writers. + + def flush_stream(self): + if hasattr(self.stream, 'flush'): + self.stream.flush() + + def write_stream_start(self): + # Write BOM if needed. + if self.encoding and self.encoding.startswith('utf-16'): + self.stream.write('\uFEFF'.encode(self.encoding)) + + def write_stream_end(self): + self.flush_stream() + + def write_indicator(self, indicator, need_whitespace, + whitespace=False, indention=False): + if self.whitespace or not need_whitespace: + data = indicator + else: + data = ' '+indicator + self.whitespace = whitespace + self.indention = self.indention and indention + self.column += len(data) + self.open_ended = False + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_indent(self): + indent = self.indent or 0 + if not self.indention or self.column > indent \ + or (self.column == indent and not self.whitespace): + self.write_line_break() + if self.column < indent: + self.whitespace = True + data = ' '*(indent-self.column) + self.column = indent + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_line_break(self, data=None): + if data is None: + data = self.best_line_break + self.whitespace = True + self.indention = True + self.line += 1 + self.column = 0 + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_version_directive(self, version_text): + data = '%%YAML %s' % version_text + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_line_break() + + def write_tag_directive(self, handle_text, prefix_text): + data = '%%TAG %s %s' % (handle_text, prefix_text) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_line_break() + + # Scalar streams. + + def write_single_quoted(self, text, split=True): + self.write_indicator('\'', True) + spaces = False + breaks = False + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if spaces: + if ch is None or ch != ' ': + if start+1 == end and self.column > self.best_width and split \ + and start != 0 and end != len(text): + self.write_indent() + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + elif breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + if text[start] == '\n': + self.write_line_break() + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + self.write_indent() + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029' or ch == '\'': + if start < end: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch == '\'': + data = '\'\'' + self.column += 2 + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + 1 + if ch is not None: + spaces = (ch == ' ') + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 + self.write_indicator('\'', False) + + ESCAPE_REPLACEMENTS = { + '\0': '0', + '\x07': 'a', + '\x08': 'b', + '\x09': 't', + '\x0A': 'n', + '\x0B': 'v', + '\x0C': 'f', + '\x0D': 'r', + '\x1B': 'e', + '\"': '\"', + '\\': '\\', + '\x85': 'N', + '\xA0': '_', + '\u2028': 'L', + '\u2029': 'P', + } + + def write_double_quoted(self, text, split=True): + self.write_indicator('"', True) + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if ch is None or ch in '"\\\x85\u2028\u2029\uFEFF' \ + or not ('\x20' <= ch <= '\x7E' + or (self.allow_unicode + and ('\xA0' <= ch <= '\uD7FF' + or '\uE000' <= ch <= '\uFFFD'))): + if start < end: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch is not None: + if ch in self.ESCAPE_REPLACEMENTS: + data = '\\'+self.ESCAPE_REPLACEMENTS[ch] + elif ch <= '\xFF': + data = '\\x%02X' % ord(ch) + elif ch <= '\uFFFF': + data = '\\u%04X' % ord(ch) + else: + data = '\\U%08X' % ord(ch) + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end+1 + if 0 < end < len(text)-1 and (ch == ' ' or start >= end) \ + and self.column+(end-start) > self.best_width and split: + data = text[start:end]+'\\' + if start < end: + start = end + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_indent() + self.whitespace = False + self.indention = False + if text[start] == ' ': + data = '\\' + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + end += 1 + self.write_indicator('"', False) + + def determine_block_hints(self, text): + hints = '' + if text: + if text[0] in ' \n\x85\u2028\u2029': + hints += str(self.best_indent) + if text[-1] not in '\n\x85\u2028\u2029': + hints += '-' + elif len(text) == 1 or text[-2] in '\n\x85\u2028\u2029': + hints += '+' + return hints + + def write_folded(self, text): + hints = self.determine_block_hints(text) + self.write_indicator('>'+hints, True) + if hints[-1:] == '+': + self.open_ended = True + self.write_line_break() + leading_space = True + spaces = False + breaks = True + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + if not leading_space and ch is not None and ch != ' ' \ + and text[start] == '\n': + self.write_line_break() + leading_space = (ch == ' ') + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + if ch is not None: + self.write_indent() + start = end + elif spaces: + if ch != ' ': + if start+1 == end and self.column > self.best_width: + self.write_indent() + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029': + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + if ch is None: + self.write_line_break() + start = end + if ch is not None: + breaks = (ch in '\n\x85\u2028\u2029') + spaces = (ch == ' ') + end += 1 + + def write_literal(self, text): + hints = self.determine_block_hints(text) + self.write_indicator('|'+hints, True) + if hints[-1:] == '+': + self.open_ended = True + self.write_line_break() + breaks = True + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + if ch is not None: + self.write_indent() + start = end + else: + if ch is None or ch in '\n\x85\u2028\u2029': + data = text[start:end] + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + if ch is None: + self.write_line_break() + start = end + if ch is not None: + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 + + def write_plain(self, text, split=True): + if self.root_context: + self.open_ended = True + if not text: + return + if not self.whitespace: + data = ' ' + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.whitespace = False + self.indention = False + spaces = False + breaks = False + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if spaces: + if ch != ' ': + if start+1 == end and self.column > self.best_width and split: + self.write_indent() + self.whitespace = False + self.indention = False + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + elif breaks: + if ch not in '\n\x85\u2028\u2029': + if text[start] == '\n': + self.write_line_break() + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + self.write_indent() + self.whitespace = False + self.indention = False + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029': + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch is not None: + spaces = (ch == ' ') + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 diff --git a/robot/lib/python3.8/site-packages/yaml/error.py b/robot/lib/python3.8/site-packages/yaml/error.py new file mode 100644 index 0000000000000000000000000000000000000000..b796b4dc519512c4825ff539a2e6aa20f4d370d0 --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/error.py @@ -0,0 +1,75 @@ + +__all__ = ['Mark', 'YAMLError', 'MarkedYAMLError'] + +class Mark: + + def __init__(self, name, index, line, column, buffer, pointer): + self.name = name + self.index = index + self.line = line + self.column = column + self.buffer = buffer + self.pointer = pointer + + def get_snippet(self, indent=4, max_length=75): + if self.buffer is None: + return None + head = '' + start = self.pointer + while start > 0 and self.buffer[start-1] not in '\0\r\n\x85\u2028\u2029': + start -= 1 + if self.pointer-start > max_length/2-1: + head = ' ... ' + start += 5 + break + tail = '' + end = self.pointer + while end < len(self.buffer) and self.buffer[end] not in '\0\r\n\x85\u2028\u2029': + end += 1 + if end-self.pointer > max_length/2-1: + tail = ' ... ' + end -= 5 + break + snippet = self.buffer[start:end] + return ' '*indent + head + snippet + tail + '\n' \ + + ' '*(indent+self.pointer-start+len(head)) + '^' + + def __str__(self): + snippet = self.get_snippet() + where = " in \"%s\", line %d, column %d" \ + % (self.name, self.line+1, self.column+1) + if snippet is not None: + where += ":\n"+snippet + return where + +class YAMLError(Exception): + pass + +class MarkedYAMLError(YAMLError): + + def __init__(self, context=None, context_mark=None, + problem=None, problem_mark=None, note=None): + self.context = context + self.context_mark = context_mark + self.problem = problem + self.problem_mark = problem_mark + self.note = note + + def __str__(self): + lines = [] + if self.context is not None: + lines.append(self.context) + if self.context_mark is not None \ + and (self.problem is None or self.problem_mark is None + or self.context_mark.name != self.problem_mark.name + or self.context_mark.line != self.problem_mark.line + or self.context_mark.column != self.problem_mark.column): + lines.append(str(self.context_mark)) + if self.problem is not None: + lines.append(self.problem) + if self.problem_mark is not None: + lines.append(str(self.problem_mark)) + if self.note is not None: + lines.append(self.note) + return '\n'.join(lines) + diff --git a/robot/lib/python3.8/site-packages/yaml/events.py b/robot/lib/python3.8/site-packages/yaml/events.py new file mode 100644 index 0000000000000000000000000000000000000000..f79ad389cb6c9517e391dcd25534866bc9ccd36a --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/events.py @@ -0,0 +1,86 @@ + +# Abstract classes. + +class Event(object): + def __init__(self, start_mark=None, end_mark=None): + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + attributes = [key for key in ['anchor', 'tag', 'implicit', 'value'] + if hasattr(self, key)] + arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) + for key in attributes]) + return '%s(%s)' % (self.__class__.__name__, arguments) + +class NodeEvent(Event): + def __init__(self, anchor, start_mark=None, end_mark=None): + self.anchor = anchor + self.start_mark = start_mark + self.end_mark = end_mark + +class CollectionStartEvent(NodeEvent): + def __init__(self, anchor, tag, implicit, start_mark=None, end_mark=None, + flow_style=None): + self.anchor = anchor + self.tag = tag + self.implicit = implicit + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + +class CollectionEndEvent(Event): + pass + +# Implementations. + +class StreamStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + +class StreamEndEvent(Event): + pass + +class DocumentStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, + explicit=None, version=None, tags=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + self.version = version + self.tags = tags + +class DocumentEndEvent(Event): + def __init__(self, start_mark=None, end_mark=None, + explicit=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + +class AliasEvent(NodeEvent): + pass + +class ScalarEvent(NodeEvent): + def __init__(self, anchor, tag, implicit, value, + start_mark=None, end_mark=None, style=None): + self.anchor = anchor + self.tag = tag + self.implicit = implicit + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + +class SequenceStartEvent(CollectionStartEvent): + pass + +class SequenceEndEvent(CollectionEndEvent): + pass + +class MappingStartEvent(CollectionStartEvent): + pass + +class MappingEndEvent(CollectionEndEvent): + pass + diff --git a/robot/lib/python3.8/site-packages/yaml/loader.py b/robot/lib/python3.8/site-packages/yaml/loader.py new file mode 100644 index 0000000000000000000000000000000000000000..e90c11224c38e559cdf0cb205f0692ebd4fb8681 --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/loader.py @@ -0,0 +1,63 @@ + +__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader'] + +from .reader import * +from .scanner import * +from .parser import * +from .composer import * +from .constructor import * +from .resolver import * + +class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + BaseConstructor.__init__(self) + BaseResolver.__init__(self) + +class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + FullConstructor.__init__(self) + Resolver.__init__(self) + +class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + SafeConstructor.__init__(self) + Resolver.__init__(self) + +class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + Constructor.__init__(self) + Resolver.__init__(self) + +# UnsafeLoader is the same as Loader (which is and was always unsafe on +# untrusted input). Use of either Loader or UnsafeLoader should be rare, since +# FullLoad should be able to load almost all YAML safely. Loader is left intact +# to ensure backwards compatibility. +class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + Constructor.__init__(self) + Resolver.__init__(self) diff --git a/robot/lib/python3.8/site-packages/yaml/nodes.py b/robot/lib/python3.8/site-packages/yaml/nodes.py new file mode 100644 index 0000000000000000000000000000000000000000..c4f070c41e1fb1bc01af27d69329e92dded38908 --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/nodes.py @@ -0,0 +1,49 @@ + +class Node(object): + def __init__(self, tag, value, start_mark, end_mark): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + value = self.value + #if isinstance(value, list): + # if len(value) == 0: + # value = '' + # elif len(value) == 1: + # value = '<1 item>' + # else: + # value = '<%d items>' % len(value) + #else: + # if len(value) > 75: + # value = repr(value[:70]+u' ... ') + # else: + # value = repr(value) + value = repr(value) + return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value) + +class ScalarNode(Node): + id = 'scalar' + def __init__(self, tag, value, + start_mark=None, end_mark=None, style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + +class CollectionNode(Node): + def __init__(self, tag, value, + start_mark=None, end_mark=None, flow_style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + +class SequenceNode(CollectionNode): + id = 'sequence' + +class MappingNode(CollectionNode): + id = 'mapping' + diff --git a/robot/lib/python3.8/site-packages/yaml/parser.py b/robot/lib/python3.8/site-packages/yaml/parser.py new file mode 100644 index 0000000000000000000000000000000000000000..13a5995d292045d0f865a99abf692bd35dc87814 --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/parser.py @@ -0,0 +1,589 @@ + +# The following YAML grammar is LL(1) and is parsed by a recursive descent +# parser. +# +# stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +# implicit_document ::= block_node DOCUMENT-END* +# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +# block_node_or_indentless_sequence ::= +# ALIAS +# | properties (block_content | indentless_block_sequence)? +# | block_content +# | indentless_block_sequence +# block_node ::= ALIAS +# | properties block_content? +# | block_content +# flow_node ::= ALIAS +# | properties flow_content? +# | flow_content +# properties ::= TAG ANCHOR? | ANCHOR TAG? +# block_content ::= block_collection | flow_collection | SCALAR +# flow_content ::= flow_collection | SCALAR +# block_collection ::= block_sequence | block_mapping +# flow_collection ::= flow_sequence | flow_mapping +# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +# indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +# block_mapping ::= BLOCK-MAPPING_START +# ((KEY block_node_or_indentless_sequence?)? +# (VALUE block_node_or_indentless_sequence?)?)* +# BLOCK-END +# flow_sequence ::= FLOW-SEQUENCE-START +# (flow_sequence_entry FLOW-ENTRY)* +# flow_sequence_entry? +# FLOW-SEQUENCE-END +# flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +# flow_mapping ::= FLOW-MAPPING-START +# (flow_mapping_entry FLOW-ENTRY)* +# flow_mapping_entry? +# FLOW-MAPPING-END +# flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +# +# FIRST sets: +# +# stream: { STREAM-START } +# explicit_document: { DIRECTIVE DOCUMENT-START } +# implicit_document: FIRST(block_node) +# block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START } +# flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START } +# block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } +# flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } +# block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START } +# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } +# block_sequence: { BLOCK-SEQUENCE-START } +# block_mapping: { BLOCK-MAPPING-START } +# block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY } +# indentless_sequence: { ENTRY } +# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } +# flow_sequence: { FLOW-SEQUENCE-START } +# flow_mapping: { FLOW-MAPPING-START } +# flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } +# flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } + +__all__ = ['Parser', 'ParserError'] + +from .error import MarkedYAMLError +from .tokens import * +from .events import * +from .scanner import * + +class ParserError(MarkedYAMLError): + pass + +class Parser: + # Since writing a recursive-descendant parser is a straightforward task, we + # do not give many comments here. + + DEFAULT_TAGS = { + '!': '!', + '!!': 'tag:yaml.org,2002:', + } + + def __init__(self): + self.current_event = None + self.yaml_version = None + self.tag_handles = {} + self.states = [] + self.marks = [] + self.state = self.parse_stream_start + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def check_event(self, *choices): + # Check the type of the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + if self.current_event is not None: + if not choices: + return True + for choice in choices: + if isinstance(self.current_event, choice): + return True + return False + + def peek_event(self): + # Get the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + return self.current_event + + def get_event(self): + # Get the next event and proceed further. + if self.current_event is None: + if self.state: + self.current_event = self.state() + value = self.current_event + self.current_event = None + return value + + # stream ::= STREAM-START implicit_document? explicit_document* STREAM-END + # implicit_document ::= block_node DOCUMENT-END* + # explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* + + def parse_stream_start(self): + + # Parse the stream start. + token = self.get_token() + event = StreamStartEvent(token.start_mark, token.end_mark, + encoding=token.encoding) + + # Prepare the next state. + self.state = self.parse_implicit_document_start + + return event + + def parse_implicit_document_start(self): + + # Parse an implicit document. + if not self.check_token(DirectiveToken, DocumentStartToken, + StreamEndToken): + self.tag_handles = self.DEFAULT_TAGS + token = self.peek_token() + start_mark = end_mark = token.start_mark + event = DocumentStartEvent(start_mark, end_mark, + explicit=False) + + # Prepare the next state. + self.states.append(self.parse_document_end) + self.state = self.parse_block_node + + return event + + else: + return self.parse_document_start() + + def parse_document_start(self): + + # Parse any extra document end indicators. + while self.check_token(DocumentEndToken): + self.get_token() + + # Parse an explicit document. + if not self.check_token(StreamEndToken): + token = self.peek_token() + start_mark = token.start_mark + version, tags = self.process_directives() + if not self.check_token(DocumentStartToken): + raise ParserError(None, None, + "expected '', but found %r" + % self.peek_token().id, + self.peek_token().start_mark) + token = self.get_token() + end_mark = token.end_mark + event = DocumentStartEvent(start_mark, end_mark, + explicit=True, version=version, tags=tags) + self.states.append(self.parse_document_end) + self.state = self.parse_document_content + else: + # Parse the end of the stream. + token = self.get_token() + event = StreamEndEvent(token.start_mark, token.end_mark) + assert not self.states + assert not self.marks + self.state = None + return event + + def parse_document_end(self): + + # Parse the document end. + token = self.peek_token() + start_mark = end_mark = token.start_mark + explicit = False + if self.check_token(DocumentEndToken): + token = self.get_token() + end_mark = token.end_mark + explicit = True + event = DocumentEndEvent(start_mark, end_mark, + explicit=explicit) + + # Prepare the next state. + self.state = self.parse_document_start + + return event + + def parse_document_content(self): + if self.check_token(DirectiveToken, + DocumentStartToken, DocumentEndToken, StreamEndToken): + event = self.process_empty_scalar(self.peek_token().start_mark) + self.state = self.states.pop() + return event + else: + return self.parse_block_node() + + def process_directives(self): + self.yaml_version = None + self.tag_handles = {} + while self.check_token(DirectiveToken): + token = self.get_token() + if token.name == 'YAML': + if self.yaml_version is not None: + raise ParserError(None, None, + "found duplicate YAML directive", token.start_mark) + major, minor = token.value + if major != 1: + raise ParserError(None, None, + "found incompatible YAML document (version 1.* is required)", + token.start_mark) + self.yaml_version = token.value + elif token.name == 'TAG': + handle, prefix = token.value + if handle in self.tag_handles: + raise ParserError(None, None, + "duplicate tag handle %r" % handle, + token.start_mark) + self.tag_handles[handle] = prefix + if self.tag_handles: + value = self.yaml_version, self.tag_handles.copy() + else: + value = self.yaml_version, None + for key in self.DEFAULT_TAGS: + if key not in self.tag_handles: + self.tag_handles[key] = self.DEFAULT_TAGS[key] + return value + + # block_node_or_indentless_sequence ::= ALIAS + # | properties (block_content | indentless_block_sequence)? + # | block_content + # | indentless_block_sequence + # block_node ::= ALIAS + # | properties block_content? + # | block_content + # flow_node ::= ALIAS + # | properties flow_content? + # | flow_content + # properties ::= TAG ANCHOR? | ANCHOR TAG? + # block_content ::= block_collection | flow_collection | SCALAR + # flow_content ::= flow_collection | SCALAR + # block_collection ::= block_sequence | block_mapping + # flow_collection ::= flow_sequence | flow_mapping + + def parse_block_node(self): + return self.parse_node(block=True) + + def parse_flow_node(self): + return self.parse_node() + + def parse_block_node_or_indentless_sequence(self): + return self.parse_node(block=True, indentless_sequence=True) + + def parse_node(self, block=False, indentless_sequence=False): + if self.check_token(AliasToken): + token = self.get_token() + event = AliasEvent(token.value, token.start_mark, token.end_mark) + self.state = self.states.pop() + else: + anchor = None + tag = None + start_mark = end_mark = tag_mark = None + if self.check_token(AnchorToken): + token = self.get_token() + start_mark = token.start_mark + end_mark = token.end_mark + anchor = token.value + if self.check_token(TagToken): + token = self.get_token() + tag_mark = token.start_mark + end_mark = token.end_mark + tag = token.value + elif self.check_token(TagToken): + token = self.get_token() + start_mark = tag_mark = token.start_mark + end_mark = token.end_mark + tag = token.value + if self.check_token(AnchorToken): + token = self.get_token() + end_mark = token.end_mark + anchor = token.value + if tag is not None: + handle, suffix = tag + if handle is not None: + if handle not in self.tag_handles: + raise ParserError("while parsing a node", start_mark, + "found undefined tag handle %r" % handle, + tag_mark) + tag = self.tag_handles[handle]+suffix + else: + tag = suffix + #if tag == '!': + # raise ParserError("while parsing a node", start_mark, + # "found non-specific tag '!'", tag_mark, + # "Please check 'http://pyyaml.org/wiki/YAMLNonSpecificTag' and share your opinion.") + if start_mark is None: + start_mark = end_mark = self.peek_token().start_mark + event = None + implicit = (tag is None or tag == '!') + if indentless_sequence and self.check_token(BlockEntryToken): + end_mark = self.peek_token().end_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark) + self.state = self.parse_indentless_sequence_entry + else: + if self.check_token(ScalarToken): + token = self.get_token() + end_mark = token.end_mark + if (token.plain and tag is None) or tag == '!': + implicit = (True, False) + elif tag is None: + implicit = (False, True) + else: + implicit = (False, False) + event = ScalarEvent(anchor, tag, implicit, token.value, + start_mark, end_mark, style=token.style) + self.state = self.states.pop() + elif self.check_token(FlowSequenceStartToken): + end_mark = self.peek_token().end_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_sequence_first_entry + elif self.check_token(FlowMappingStartToken): + end_mark = self.peek_token().end_mark + event = MappingStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_mapping_first_key + elif block and self.check_token(BlockSequenceStartToken): + end_mark = self.peek_token().start_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=False) + self.state = self.parse_block_sequence_first_entry + elif block and self.check_token(BlockMappingStartToken): + end_mark = self.peek_token().start_mark + event = MappingStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=False) + self.state = self.parse_block_mapping_first_key + elif anchor is not None or tag is not None: + # Empty scalars are allowed even if a tag or an anchor is + # specified. + event = ScalarEvent(anchor, tag, (implicit, False), '', + start_mark, end_mark) + self.state = self.states.pop() + else: + if block: + node = 'block' + else: + node = 'flow' + token = self.peek_token() + raise ParserError("while parsing a %s node" % node, start_mark, + "expected the node content, but found %r" % token.id, + token.start_mark) + return event + + # block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END + + def parse_block_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_block_sequence_entry() + + def parse_block_sequence_entry(self): + if self.check_token(BlockEntryToken): + token = self.get_token() + if not self.check_token(BlockEntryToken, BlockEndToken): + self.states.append(self.parse_block_sequence_entry) + return self.parse_block_node() + else: + self.state = self.parse_block_sequence_entry + return self.process_empty_scalar(token.end_mark) + if not self.check_token(BlockEndToken): + token = self.peek_token() + raise ParserError("while parsing a block collection", self.marks[-1], + "expected , but found %r" % token.id, token.start_mark) + token = self.get_token() + event = SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + # indentless_sequence ::= (BLOCK-ENTRY block_node?)+ + + def parse_indentless_sequence_entry(self): + if self.check_token(BlockEntryToken): + token = self.get_token() + if not self.check_token(BlockEntryToken, + KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_indentless_sequence_entry) + return self.parse_block_node() + else: + self.state = self.parse_indentless_sequence_entry + return self.process_empty_scalar(token.end_mark) + token = self.peek_token() + event = SequenceEndEvent(token.start_mark, token.start_mark) + self.state = self.states.pop() + return event + + # block_mapping ::= BLOCK-MAPPING_START + # ((KEY block_node_or_indentless_sequence?)? + # (VALUE block_node_or_indentless_sequence?)?)* + # BLOCK-END + + def parse_block_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_block_mapping_key() + + def parse_block_mapping_key(self): + if self.check_token(KeyToken): + token = self.get_token() + if not self.check_token(KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_block_mapping_value) + return self.parse_block_node_or_indentless_sequence() + else: + self.state = self.parse_block_mapping_value + return self.process_empty_scalar(token.end_mark) + if not self.check_token(BlockEndToken): + token = self.peek_token() + raise ParserError("while parsing a block mapping", self.marks[-1], + "expected , but found %r" % token.id, token.start_mark) + token = self.get_token() + event = MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_block_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_block_mapping_key) + return self.parse_block_node_or_indentless_sequence() + else: + self.state = self.parse_block_mapping_key + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_block_mapping_key + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + # flow_sequence ::= FLOW-SEQUENCE-START + # (flow_sequence_entry FLOW-ENTRY)* + # flow_sequence_entry? + # FLOW-SEQUENCE-END + # flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + # + # Note that while production rules for both flow_sequence_entry and + # flow_mapping_entry are equal, their interpretations are different. + # For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?` + # generate an inline mapping (set syntax). + + def parse_flow_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_sequence_entry(first=True) + + def parse_flow_sequence_entry(self, first=False): + if not self.check_token(FlowSequenceEndToken): + if not first: + if self.check_token(FlowEntryToken): + self.get_token() + else: + token = self.peek_token() + raise ParserError("while parsing a flow sequence", self.marks[-1], + "expected ',' or ']', but got %r" % token.id, token.start_mark) + + if self.check_token(KeyToken): + token = self.peek_token() + event = MappingStartEvent(None, None, True, + token.start_mark, token.end_mark, + flow_style=True) + self.state = self.parse_flow_sequence_entry_mapping_key + return event + elif not self.check_token(FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry) + return self.parse_flow_node() + token = self.get_token() + event = SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_sequence_entry_mapping_key(self): + token = self.get_token() + if not self.check_token(ValueToken, + FlowEntryToken, FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry_mapping_value) + return self.parse_flow_node() + else: + self.state = self.parse_flow_sequence_entry_mapping_value + return self.process_empty_scalar(token.end_mark) + + def parse_flow_sequence_entry_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(FlowEntryToken, FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry_mapping_end) + return self.parse_flow_node() + else: + self.state = self.parse_flow_sequence_entry_mapping_end + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_flow_sequence_entry_mapping_end + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + def parse_flow_sequence_entry_mapping_end(self): + self.state = self.parse_flow_sequence_entry + token = self.peek_token() + return MappingEndEvent(token.start_mark, token.start_mark) + + # flow_mapping ::= FLOW-MAPPING-START + # (flow_mapping_entry FLOW-ENTRY)* + # flow_mapping_entry? + # FLOW-MAPPING-END + # flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + + def parse_flow_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_mapping_key(first=True) + + def parse_flow_mapping_key(self, first=False): + if not self.check_token(FlowMappingEndToken): + if not first: + if self.check_token(FlowEntryToken): + self.get_token() + else: + token = self.peek_token() + raise ParserError("while parsing a flow mapping", self.marks[-1], + "expected ',' or '}', but got %r" % token.id, token.start_mark) + if self.check_token(KeyToken): + token = self.get_token() + if not self.check_token(ValueToken, + FlowEntryToken, FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_value) + return self.parse_flow_node() + else: + self.state = self.parse_flow_mapping_value + return self.process_empty_scalar(token.end_mark) + elif not self.check_token(FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_empty_value) + return self.parse_flow_node() + token = self.get_token() + event = MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(FlowEntryToken, FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_key) + return self.parse_flow_node() + else: + self.state = self.parse_flow_mapping_key + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_flow_mapping_key + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + def parse_flow_mapping_empty_value(self): + self.state = self.parse_flow_mapping_key + return self.process_empty_scalar(self.peek_token().start_mark) + + def process_empty_scalar(self, mark): + return ScalarEvent(None, None, (True, False), '', mark, mark) + diff --git a/robot/lib/python3.8/site-packages/yaml/reader.py b/robot/lib/python3.8/site-packages/yaml/reader.py new file mode 100644 index 0000000000000000000000000000000000000000..774b0219b5932a0ee1c27e637371de5ba8d9cb16 --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/reader.py @@ -0,0 +1,185 @@ +# This module contains abstractions for the input stream. You don't have to +# looks further, there are no pretty code. +# +# We define two classes here. +# +# Mark(source, line, column) +# It's just a record and its only use is producing nice error messages. +# Parser does not use it for any other purposes. +# +# Reader(source, data) +# Reader determines the encoding of `data` and converts it to unicode. +# Reader provides the following methods and attributes: +# reader.peek(length=1) - return the next `length` characters +# reader.forward(length=1) - move the current position to `length` characters. +# reader.index - the number of the current character. +# reader.line, stream.column - the line and the column of the current character. + +__all__ = ['Reader', 'ReaderError'] + +from .error import YAMLError, Mark + +import codecs, re + +class ReaderError(YAMLError): + + def __init__(self, name, position, character, encoding, reason): + self.name = name + self.character = character + self.position = position + self.encoding = encoding + self.reason = reason + + def __str__(self): + if isinstance(self.character, bytes): + return "'%s' codec can't decode byte #x%02x: %s\n" \ + " in \"%s\", position %d" \ + % (self.encoding, ord(self.character), self.reason, + self.name, self.position) + else: + return "unacceptable character #x%04x: %s\n" \ + " in \"%s\", position %d" \ + % (self.character, self.reason, + self.name, self.position) + +class Reader(object): + # Reader: + # - determines the data encoding and converts it to a unicode string, + # - checks if characters are in allowed range, + # - adds '\0' to the end. + + # Reader accepts + # - a `bytes` object, + # - a `str` object, + # - a file-like object with its `read` method returning `str`, + # - a file-like object with its `read` method returning `unicode`. + + # Yeah, it's ugly and slow. + + def __init__(self, stream): + self.name = None + self.stream = None + self.stream_pointer = 0 + self.eof = True + self.buffer = '' + self.pointer = 0 + self.raw_buffer = None + self.raw_decode = None + self.encoding = None + self.index = 0 + self.line = 0 + self.column = 0 + if isinstance(stream, str): + self.name = "" + self.check_printable(stream) + self.buffer = stream+'\0' + elif isinstance(stream, bytes): + self.name = "" + self.raw_buffer = stream + self.determine_encoding() + else: + self.stream = stream + self.name = getattr(stream, 'name', "") + self.eof = False + self.raw_buffer = None + self.determine_encoding() + + def peek(self, index=0): + try: + return self.buffer[self.pointer+index] + except IndexError: + self.update(index+1) + return self.buffer[self.pointer+index] + + def prefix(self, length=1): + if self.pointer+length >= len(self.buffer): + self.update(length) + return self.buffer[self.pointer:self.pointer+length] + + def forward(self, length=1): + if self.pointer+length+1 >= len(self.buffer): + self.update(length+1) + while length: + ch = self.buffer[self.pointer] + self.pointer += 1 + self.index += 1 + if ch in '\n\x85\u2028\u2029' \ + or (ch == '\r' and self.buffer[self.pointer] != '\n'): + self.line += 1 + self.column = 0 + elif ch != '\uFEFF': + self.column += 1 + length -= 1 + + def get_mark(self): + if self.stream is None: + return Mark(self.name, self.index, self.line, self.column, + self.buffer, self.pointer) + else: + return Mark(self.name, self.index, self.line, self.column, + None, None) + + def determine_encoding(self): + while not self.eof and (self.raw_buffer is None or len(self.raw_buffer) < 2): + self.update_raw() + if isinstance(self.raw_buffer, bytes): + if self.raw_buffer.startswith(codecs.BOM_UTF16_LE): + self.raw_decode = codecs.utf_16_le_decode + self.encoding = 'utf-16-le' + elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE): + self.raw_decode = codecs.utf_16_be_decode + self.encoding = 'utf-16-be' + else: + self.raw_decode = codecs.utf_8_decode + self.encoding = 'utf-8' + self.update(1) + + NON_PRINTABLE = re.compile('[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010ffff]') + def check_printable(self, data): + match = self.NON_PRINTABLE.search(data) + if match: + character = match.group() + position = self.index+(len(self.buffer)-self.pointer)+match.start() + raise ReaderError(self.name, position, ord(character), + 'unicode', "special characters are not allowed") + + def update(self, length): + if self.raw_buffer is None: + return + self.buffer = self.buffer[self.pointer:] + self.pointer = 0 + while len(self.buffer) < length: + if not self.eof: + self.update_raw() + if self.raw_decode is not None: + try: + data, converted = self.raw_decode(self.raw_buffer, + 'strict', self.eof) + except UnicodeDecodeError as exc: + character = self.raw_buffer[exc.start] + if self.stream is not None: + position = self.stream_pointer-len(self.raw_buffer)+exc.start + else: + position = exc.start + raise ReaderError(self.name, position, character, + exc.encoding, exc.reason) + else: + data = self.raw_buffer + converted = len(data) + self.check_printable(data) + self.buffer += data + self.raw_buffer = self.raw_buffer[converted:] + if self.eof: + self.buffer += '\0' + self.raw_buffer = None + break + + def update_raw(self, size=4096): + data = self.stream.read(size) + if self.raw_buffer is None: + self.raw_buffer = data + else: + self.raw_buffer += data + self.stream_pointer += len(data) + if not data: + self.eof = True diff --git a/robot/lib/python3.8/site-packages/yaml/representer.py b/robot/lib/python3.8/site-packages/yaml/representer.py new file mode 100644 index 0000000000000000000000000000000000000000..3b0b192ef32ed7f5b7015456fe883c3327bb841e --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/representer.py @@ -0,0 +1,389 @@ + +__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer', + 'RepresenterError'] + +from .error import * +from .nodes import * + +import datetime, copyreg, types, base64, collections + +class RepresenterError(YAMLError): + pass + +class BaseRepresenter: + + yaml_representers = {} + yaml_multi_representers = {} + + def __init__(self, default_style=None, default_flow_style=False, sort_keys=True): + self.default_style = default_style + self.sort_keys = sort_keys + self.default_flow_style = default_flow_style + self.represented_objects = {} + self.object_keeper = [] + self.alias_key = None + + def represent(self, data): + node = self.represent_data(data) + self.serialize(node) + self.represented_objects = {} + self.object_keeper = [] + self.alias_key = None + + def represent_data(self, data): + if self.ignore_aliases(data): + self.alias_key = None + else: + self.alias_key = id(data) + if self.alias_key is not None: + if self.alias_key in self.represented_objects: + node = self.represented_objects[self.alias_key] + #if node is None: + # raise RepresenterError("recursive objects are not allowed: %r" % data) + return node + #self.represented_objects[alias_key] = None + self.object_keeper.append(data) + data_types = type(data).__mro__ + if data_types[0] in self.yaml_representers: + node = self.yaml_representers[data_types[0]](self, data) + else: + for data_type in data_types: + if data_type in self.yaml_multi_representers: + node = self.yaml_multi_representers[data_type](self, data) + break + else: + if None in self.yaml_multi_representers: + node = self.yaml_multi_representers[None](self, data) + elif None in self.yaml_representers: + node = self.yaml_representers[None](self, data) + else: + node = ScalarNode(None, str(data)) + #if alias_key is not None: + # self.represented_objects[alias_key] = node + return node + + @classmethod + def add_representer(cls, data_type, representer): + if not 'yaml_representers' in cls.__dict__: + cls.yaml_representers = cls.yaml_representers.copy() + cls.yaml_representers[data_type] = representer + + @classmethod + def add_multi_representer(cls, data_type, representer): + if not 'yaml_multi_representers' in cls.__dict__: + cls.yaml_multi_representers = cls.yaml_multi_representers.copy() + cls.yaml_multi_representers[data_type] = representer + + def represent_scalar(self, tag, value, style=None): + if style is None: + style = self.default_style + node = ScalarNode(tag, value, style=style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + return node + + def represent_sequence(self, tag, sequence, flow_style=None): + value = [] + node = SequenceNode(tag, value, flow_style=flow_style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + best_style = True + for item in sequence: + node_item = self.represent_data(item) + if not (isinstance(node_item, ScalarNode) and not node_item.style): + best_style = False + value.append(node_item) + if flow_style is None: + if self.default_flow_style is not None: + node.flow_style = self.default_flow_style + else: + node.flow_style = best_style + return node + + def represent_mapping(self, tag, mapping, flow_style=None): + value = [] + node = MappingNode(tag, value, flow_style=flow_style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + best_style = True + if hasattr(mapping, 'items'): + mapping = list(mapping.items()) + if self.sort_keys: + try: + mapping = sorted(mapping) + except TypeError: + pass + for item_key, item_value in mapping: + node_key = self.represent_data(item_key) + node_value = self.represent_data(item_value) + if not (isinstance(node_key, ScalarNode) and not node_key.style): + best_style = False + if not (isinstance(node_value, ScalarNode) and not node_value.style): + best_style = False + value.append((node_key, node_value)) + if flow_style is None: + if self.default_flow_style is not None: + node.flow_style = self.default_flow_style + else: + node.flow_style = best_style + return node + + def ignore_aliases(self, data): + return False + +class SafeRepresenter(BaseRepresenter): + + def ignore_aliases(self, data): + if data is None: + return True + if isinstance(data, tuple) and data == (): + return True + if isinstance(data, (str, bytes, bool, int, float)): + return True + + def represent_none(self, data): + return self.represent_scalar('tag:yaml.org,2002:null', 'null') + + def represent_str(self, data): + return self.represent_scalar('tag:yaml.org,2002:str', data) + + def represent_binary(self, data): + if hasattr(base64, 'encodebytes'): + data = base64.encodebytes(data).decode('ascii') + else: + data = base64.encodestring(data).decode('ascii') + return self.represent_scalar('tag:yaml.org,2002:binary', data, style='|') + + def represent_bool(self, data): + if data: + value = 'true' + else: + value = 'false' + return self.represent_scalar('tag:yaml.org,2002:bool', value) + + def represent_int(self, data): + return self.represent_scalar('tag:yaml.org,2002:int', str(data)) + + inf_value = 1e300 + while repr(inf_value) != repr(inf_value*inf_value): + inf_value *= inf_value + + def represent_float(self, data): + if data != data or (data == 0.0 and data == 1.0): + value = '.nan' + elif data == self.inf_value: + value = '.inf' + elif data == -self.inf_value: + value = '-.inf' + else: + value = repr(data).lower() + # Note that in some cases `repr(data)` represents a float number + # without the decimal parts. For instance: + # >>> repr(1e17) + # '1e17' + # Unfortunately, this is not a valid float representation according + # to the definition of the `!!float` tag. We fix this by adding + # '.0' before the 'e' symbol. + if '.' not in value and 'e' in value: + value = value.replace('e', '.0e', 1) + return self.represent_scalar('tag:yaml.org,2002:float', value) + + def represent_list(self, data): + #pairs = (len(data) > 0 and isinstance(data, list)) + #if pairs: + # for item in data: + # if not isinstance(item, tuple) or len(item) != 2: + # pairs = False + # break + #if not pairs: + return self.represent_sequence('tag:yaml.org,2002:seq', data) + #value = [] + #for item_key, item_value in data: + # value.append(self.represent_mapping(u'tag:yaml.org,2002:map', + # [(item_key, item_value)])) + #return SequenceNode(u'tag:yaml.org,2002:pairs', value) + + def represent_dict(self, data): + return self.represent_mapping('tag:yaml.org,2002:map', data) + + def represent_set(self, data): + value = {} + for key in data: + value[key] = None + return self.represent_mapping('tag:yaml.org,2002:set', value) + + def represent_date(self, data): + value = data.isoformat() + return self.represent_scalar('tag:yaml.org,2002:timestamp', value) + + def represent_datetime(self, data): + value = data.isoformat(' ') + return self.represent_scalar('tag:yaml.org,2002:timestamp', value) + + def represent_yaml_object(self, tag, data, cls, flow_style=None): + if hasattr(data, '__getstate__'): + state = data.__getstate__() + else: + state = data.__dict__.copy() + return self.represent_mapping(tag, state, flow_style=flow_style) + + def represent_undefined(self, data): + raise RepresenterError("cannot represent an object", data) + +SafeRepresenter.add_representer(type(None), + SafeRepresenter.represent_none) + +SafeRepresenter.add_representer(str, + SafeRepresenter.represent_str) + +SafeRepresenter.add_representer(bytes, + SafeRepresenter.represent_binary) + +SafeRepresenter.add_representer(bool, + SafeRepresenter.represent_bool) + +SafeRepresenter.add_representer(int, + SafeRepresenter.represent_int) + +SafeRepresenter.add_representer(float, + SafeRepresenter.represent_float) + +SafeRepresenter.add_representer(list, + SafeRepresenter.represent_list) + +SafeRepresenter.add_representer(tuple, + SafeRepresenter.represent_list) + +SafeRepresenter.add_representer(dict, + SafeRepresenter.represent_dict) + +SafeRepresenter.add_representer(set, + SafeRepresenter.represent_set) + +SafeRepresenter.add_representer(datetime.date, + SafeRepresenter.represent_date) + +SafeRepresenter.add_representer(datetime.datetime, + SafeRepresenter.represent_datetime) + +SafeRepresenter.add_representer(None, + SafeRepresenter.represent_undefined) + +class Representer(SafeRepresenter): + + def represent_complex(self, data): + if data.imag == 0.0: + data = '%r' % data.real + elif data.real == 0.0: + data = '%rj' % data.imag + elif data.imag > 0: + data = '%r+%rj' % (data.real, data.imag) + else: + data = '%r%rj' % (data.real, data.imag) + return self.represent_scalar('tag:yaml.org,2002:python/complex', data) + + def represent_tuple(self, data): + return self.represent_sequence('tag:yaml.org,2002:python/tuple', data) + + def represent_name(self, data): + name = '%s.%s' % (data.__module__, data.__name__) + return self.represent_scalar('tag:yaml.org,2002:python/name:'+name, '') + + def represent_module(self, data): + return self.represent_scalar( + 'tag:yaml.org,2002:python/module:'+data.__name__, '') + + def represent_object(self, data): + # We use __reduce__ API to save the data. data.__reduce__ returns + # a tuple of length 2-5: + # (function, args, state, listitems, dictitems) + + # For reconstructing, we calls function(*args), then set its state, + # listitems, and dictitems if they are not None. + + # A special case is when function.__name__ == '__newobj__'. In this + # case we create the object with args[0].__new__(*args). + + # Another special case is when __reduce__ returns a string - we don't + # support it. + + # We produce a !!python/object, !!python/object/new or + # !!python/object/apply node. + + cls = type(data) + if cls in copyreg.dispatch_table: + reduce = copyreg.dispatch_table[cls](data) + elif hasattr(data, '__reduce_ex__'): + reduce = data.__reduce_ex__(2) + elif hasattr(data, '__reduce__'): + reduce = data.__reduce__() + else: + raise RepresenterError("cannot represent an object", data) + reduce = (list(reduce)+[None]*5)[:5] + function, args, state, listitems, dictitems = reduce + args = list(args) + if state is None: + state = {} + if listitems is not None: + listitems = list(listitems) + if dictitems is not None: + dictitems = dict(dictitems) + if function.__name__ == '__newobj__': + function = args[0] + args = args[1:] + tag = 'tag:yaml.org,2002:python/object/new:' + newobj = True + else: + tag = 'tag:yaml.org,2002:python/object/apply:' + newobj = False + function_name = '%s.%s' % (function.__module__, function.__name__) + if not args and not listitems and not dictitems \ + and isinstance(state, dict) and newobj: + return self.represent_mapping( + 'tag:yaml.org,2002:python/object:'+function_name, state) + if not listitems and not dictitems \ + and isinstance(state, dict) and not state: + return self.represent_sequence(tag+function_name, args) + value = {} + if args: + value['args'] = args + if state or not isinstance(state, dict): + value['state'] = state + if listitems: + value['listitems'] = listitems + if dictitems: + value['dictitems'] = dictitems + return self.represent_mapping(tag+function_name, value) + + def represent_ordered_dict(self, data): + # Provide uniform representation across different Python versions. + data_type = type(data) + tag = 'tag:yaml.org,2002:python/object/apply:%s.%s' \ + % (data_type.__module__, data_type.__name__) + items = [[key, value] for key, value in data.items()] + return self.represent_sequence(tag, [items]) + +Representer.add_representer(complex, + Representer.represent_complex) + +Representer.add_representer(tuple, + Representer.represent_tuple) + +Representer.add_representer(type, + Representer.represent_name) + +Representer.add_representer(collections.OrderedDict, + Representer.represent_ordered_dict) + +Representer.add_representer(types.FunctionType, + Representer.represent_name) + +Representer.add_representer(types.BuiltinFunctionType, + Representer.represent_name) + +Representer.add_representer(types.ModuleType, + Representer.represent_module) + +Representer.add_multi_representer(object, + Representer.represent_object) + diff --git a/robot/lib/python3.8/site-packages/yaml/resolver.py b/robot/lib/python3.8/site-packages/yaml/resolver.py new file mode 100644 index 0000000000000000000000000000000000000000..02b82e73eecac9875336617a7ff38300ff4507ef --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/resolver.py @@ -0,0 +1,227 @@ + +__all__ = ['BaseResolver', 'Resolver'] + +from .error import * +from .nodes import * + +import re + +class ResolverError(YAMLError): + pass + +class BaseResolver: + + DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str' + DEFAULT_SEQUENCE_TAG = 'tag:yaml.org,2002:seq' + DEFAULT_MAPPING_TAG = 'tag:yaml.org,2002:map' + + yaml_implicit_resolvers = {} + yaml_path_resolvers = {} + + def __init__(self): + self.resolver_exact_paths = [] + self.resolver_prefix_paths = [] + + @classmethod + def add_implicit_resolver(cls, tag, regexp, first): + if not 'yaml_implicit_resolvers' in cls.__dict__: + implicit_resolvers = {} + for key in cls.yaml_implicit_resolvers: + implicit_resolvers[key] = cls.yaml_implicit_resolvers[key][:] + cls.yaml_implicit_resolvers = implicit_resolvers + if first is None: + first = [None] + for ch in first: + cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp)) + + @classmethod + def add_path_resolver(cls, tag, path, kind=None): + # Note: `add_path_resolver` is experimental. The API could be changed. + # `new_path` is a pattern that is matched against the path from the + # root to the node that is being considered. `node_path` elements are + # tuples `(node_check, index_check)`. `node_check` is a node class: + # `ScalarNode`, `SequenceNode`, `MappingNode` or `None`. `None` + # matches any kind of a node. `index_check` could be `None`, a boolean + # value, a string value, or a number. `None` and `False` match against + # any _value_ of sequence and mapping nodes. `True` matches against + # any _key_ of a mapping node. A string `index_check` matches against + # a mapping value that corresponds to a scalar key which content is + # equal to the `index_check` value. An integer `index_check` matches + # against a sequence value with the index equal to `index_check`. + if not 'yaml_path_resolvers' in cls.__dict__: + cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy() + new_path = [] + for element in path: + if isinstance(element, (list, tuple)): + if len(element) == 2: + node_check, index_check = element + elif len(element) == 1: + node_check = element[0] + index_check = True + else: + raise ResolverError("Invalid path element: %s" % element) + else: + node_check = None + index_check = element + if node_check is str: + node_check = ScalarNode + elif node_check is list: + node_check = SequenceNode + elif node_check is dict: + node_check = MappingNode + elif node_check not in [ScalarNode, SequenceNode, MappingNode] \ + and not isinstance(node_check, str) \ + and node_check is not None: + raise ResolverError("Invalid node checker: %s" % node_check) + if not isinstance(index_check, (str, int)) \ + and index_check is not None: + raise ResolverError("Invalid index checker: %s" % index_check) + new_path.append((node_check, index_check)) + if kind is str: + kind = ScalarNode + elif kind is list: + kind = SequenceNode + elif kind is dict: + kind = MappingNode + elif kind not in [ScalarNode, SequenceNode, MappingNode] \ + and kind is not None: + raise ResolverError("Invalid node kind: %s" % kind) + cls.yaml_path_resolvers[tuple(new_path), kind] = tag + + def descend_resolver(self, current_node, current_index): + if not self.yaml_path_resolvers: + return + exact_paths = {} + prefix_paths = [] + if current_node: + depth = len(self.resolver_prefix_paths) + for path, kind in self.resolver_prefix_paths[-1]: + if self.check_resolver_prefix(depth, path, kind, + current_node, current_index): + if len(path) > depth: + prefix_paths.append((path, kind)) + else: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + for path, kind in self.yaml_path_resolvers: + if not path: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + prefix_paths.append((path, kind)) + self.resolver_exact_paths.append(exact_paths) + self.resolver_prefix_paths.append(prefix_paths) + + def ascend_resolver(self): + if not self.yaml_path_resolvers: + return + self.resolver_exact_paths.pop() + self.resolver_prefix_paths.pop() + + def check_resolver_prefix(self, depth, path, kind, + current_node, current_index): + node_check, index_check = path[depth-1] + if isinstance(node_check, str): + if current_node.tag != node_check: + return + elif node_check is not None: + if not isinstance(current_node, node_check): + return + if index_check is True and current_index is not None: + return + if (index_check is False or index_check is None) \ + and current_index is None: + return + if isinstance(index_check, str): + if not (isinstance(current_index, ScalarNode) + and index_check == current_index.value): + return + elif isinstance(index_check, int) and not isinstance(index_check, bool): + if index_check != current_index: + return + return True + + def resolve(self, kind, value, implicit): + if kind is ScalarNode and implicit[0]: + if value == '': + resolvers = self.yaml_implicit_resolvers.get('', []) + else: + resolvers = self.yaml_implicit_resolvers.get(value[0], []) + resolvers += self.yaml_implicit_resolvers.get(None, []) + for tag, regexp in resolvers: + if regexp.match(value): + return tag + implicit = implicit[1] + if self.yaml_path_resolvers: + exact_paths = self.resolver_exact_paths[-1] + if kind in exact_paths: + return exact_paths[kind] + if None in exact_paths: + return exact_paths[None] + if kind is ScalarNode: + return self.DEFAULT_SCALAR_TAG + elif kind is SequenceNode: + return self.DEFAULT_SEQUENCE_TAG + elif kind is MappingNode: + return self.DEFAULT_MAPPING_TAG + +class Resolver(BaseResolver): + pass + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:bool', + re.compile(r'''^(?:yes|Yes|YES|no|No|NO + |true|True|TRUE|false|False|FALSE + |on|On|ON|off|Off|OFF)$''', re.X), + list('yYnNtTfFoO')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:float', + re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? + |\.[0-9_]+(?:[eE][-+][0-9]+)? + |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]* + |[-+]?\.(?:inf|Inf|INF) + |\.(?:nan|NaN|NAN))$''', re.X), + list('-+0123456789.')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:int', + re.compile(r'''^(?:[-+]?0b[0-1_]+ + |[-+]?0[0-7_]+ + |[-+]?(?:0|[1-9][0-9_]*) + |[-+]?0x[0-9a-fA-F_]+ + |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X), + list('-+0123456789')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:merge', + re.compile(r'^(?:<<)$'), + ['<']) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:null', + re.compile(r'''^(?: ~ + |null|Null|NULL + | )$''', re.X), + ['~', 'n', 'N', '']) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:timestamp', + re.compile(r'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] + |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]? + (?:[Tt]|[ \t]+)[0-9][0-9]? + :[0-9][0-9] :[0-9][0-9] (?:\.[0-9]*)? + (?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X), + list('0123456789')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:value', + re.compile(r'^(?:=)$'), + ['=']) + +# The following resolver is only for documentation purposes. It cannot work +# because plain scalars cannot start with '!', '&', or '*'. +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:yaml', + re.compile(r'^(?:!|&|\*)$'), + list('!&*')) + diff --git a/robot/lib/python3.8/site-packages/yaml/scanner.py b/robot/lib/python3.8/site-packages/yaml/scanner.py new file mode 100644 index 0000000000000000000000000000000000000000..7437ede1c608266aaca481955f438844479cab4f --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/scanner.py @@ -0,0 +1,1435 @@ + +# Scanner produces tokens of the following types: +# STREAM-START +# STREAM-END +# DIRECTIVE(name, value) +# DOCUMENT-START +# DOCUMENT-END +# BLOCK-SEQUENCE-START +# BLOCK-MAPPING-START +# BLOCK-END +# FLOW-SEQUENCE-START +# FLOW-MAPPING-START +# FLOW-SEQUENCE-END +# FLOW-MAPPING-END +# BLOCK-ENTRY +# FLOW-ENTRY +# KEY +# VALUE +# ALIAS(value) +# ANCHOR(value) +# TAG(value) +# SCALAR(value, plain, style) +# +# Read comments in the Scanner code for more details. +# + +__all__ = ['Scanner', 'ScannerError'] + +from .error import MarkedYAMLError +from .tokens import * + +class ScannerError(MarkedYAMLError): + pass + +class SimpleKey: + # See below simple keys treatment. + + def __init__(self, token_number, required, index, line, column, mark): + self.token_number = token_number + self.required = required + self.index = index + self.line = line + self.column = column + self.mark = mark + +class Scanner: + + def __init__(self): + """Initialize the scanner.""" + # It is assumed that Scanner and Reader will have a common descendant. + # Reader do the dirty work of checking for BOM and converting the + # input data to Unicode. It also adds NUL to the end. + # + # Reader supports the following methods + # self.peek(i=0) # peek the next i-th character + # self.prefix(l=1) # peek the next l characters + # self.forward(l=1) # read the next l characters and move the pointer. + + # Had we reached the end of the stream? + self.done = False + + # The number of unclosed '{' and '['. `flow_level == 0` means block + # context. + self.flow_level = 0 + + # List of processed tokens that are not yet emitted. + self.tokens = [] + + # Add the STREAM-START token. + self.fetch_stream_start() + + # Number of tokens that were emitted through the `get_token` method. + self.tokens_taken = 0 + + # The current indentation level. + self.indent = -1 + + # Past indentation levels. + self.indents = [] + + # Variables related to simple keys treatment. + + # A simple key is a key that is not denoted by the '?' indicator. + # Example of simple keys: + # --- + # block simple key: value + # ? not a simple key: + # : { flow simple key: value } + # We emit the KEY token before all keys, so when we find a potential + # simple key, we try to locate the corresponding ':' indicator. + # Simple keys should be limited to a single line and 1024 characters. + + # Can a simple key start at the current position? A simple key may + # start: + # - at the beginning of the line, not counting indentation spaces + # (in block context), + # - after '{', '[', ',' (in the flow context), + # - after '?', ':', '-' (in the block context). + # In the block context, this flag also signifies if a block collection + # may start at the current position. + self.allow_simple_key = True + + # Keep track of possible simple keys. This is a dictionary. The key + # is `flow_level`; there can be no more that one possible simple key + # for each level. The value is a SimpleKey record: + # (token_number, required, index, line, column, mark) + # A simple key may start with ALIAS, ANCHOR, TAG, SCALAR(flow), + # '[', or '{' tokens. + self.possible_simple_keys = {} + + # Public methods. + + def check_token(self, *choices): + # Check if the next token is one of the given types. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + if not choices: + return True + for choice in choices: + if isinstance(self.tokens[0], choice): + return True + return False + + def peek_token(self): + # Return the next token, but do not delete if from the queue. + # Return None if no more tokens. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + return self.tokens[0] + else: + return None + + def get_token(self): + # Return the next token. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + self.tokens_taken += 1 + return self.tokens.pop(0) + + # Private methods. + + def need_more_tokens(self): + if self.done: + return False + if not self.tokens: + return True + # The current token may be a potential simple key, so we + # need to look further. + self.stale_possible_simple_keys() + if self.next_possible_simple_key() == self.tokens_taken: + return True + + def fetch_more_tokens(self): + + # Eat whitespaces and comments until we reach the next token. + self.scan_to_next_token() + + # Remove obsolete possible simple keys. + self.stale_possible_simple_keys() + + # Compare the current indentation and column. It may add some tokens + # and decrease the current indentation level. + self.unwind_indent(self.column) + + # Peek the next character. + ch = self.peek() + + # Is it the end of stream? + if ch == '\0': + return self.fetch_stream_end() + + # Is it a directive? + if ch == '%' and self.check_directive(): + return self.fetch_directive() + + # Is it the document start? + if ch == '-' and self.check_document_start(): + return self.fetch_document_start() + + # Is it the document end? + if ch == '.' and self.check_document_end(): + return self.fetch_document_end() + + # TODO: support for BOM within a stream. + #if ch == '\uFEFF': + # return self.fetch_bom() <-- issue BOMToken + + # Note: the order of the following checks is NOT significant. + + # Is it the flow sequence start indicator? + if ch == '[': + return self.fetch_flow_sequence_start() + + # Is it the flow mapping start indicator? + if ch == '{': + return self.fetch_flow_mapping_start() + + # Is it the flow sequence end indicator? + if ch == ']': + return self.fetch_flow_sequence_end() + + # Is it the flow mapping end indicator? + if ch == '}': + return self.fetch_flow_mapping_end() + + # Is it the flow entry indicator? + if ch == ',': + return self.fetch_flow_entry() + + # Is it the block entry indicator? + if ch == '-' and self.check_block_entry(): + return self.fetch_block_entry() + + # Is it the key indicator? + if ch == '?' and self.check_key(): + return self.fetch_key() + + # Is it the value indicator? + if ch == ':' and self.check_value(): + return self.fetch_value() + + # Is it an alias? + if ch == '*': + return self.fetch_alias() + + # Is it an anchor? + if ch == '&': + return self.fetch_anchor() + + # Is it a tag? + if ch == '!': + return self.fetch_tag() + + # Is it a literal scalar? + if ch == '|' and not self.flow_level: + return self.fetch_literal() + + # Is it a folded scalar? + if ch == '>' and not self.flow_level: + return self.fetch_folded() + + # Is it a single quoted scalar? + if ch == '\'': + return self.fetch_single() + + # Is it a double quoted scalar? + if ch == '\"': + return self.fetch_double() + + # It must be a plain scalar then. + if self.check_plain(): + return self.fetch_plain() + + # No? It's an error. Let's produce a nice error message. + raise ScannerError("while scanning for the next token", None, + "found character %r that cannot start any token" % ch, + self.get_mark()) + + # Simple keys treatment. + + def next_possible_simple_key(self): + # Return the number of the nearest possible simple key. Actually we + # don't need to loop through the whole dictionary. We may replace it + # with the following code: + # if not self.possible_simple_keys: + # return None + # return self.possible_simple_keys[ + # min(self.possible_simple_keys.keys())].token_number + min_token_number = None + for level in self.possible_simple_keys: + key = self.possible_simple_keys[level] + if min_token_number is None or key.token_number < min_token_number: + min_token_number = key.token_number + return min_token_number + + def stale_possible_simple_keys(self): + # Remove entries that are no longer possible simple keys. According to + # the YAML specification, simple keys + # - should be limited to a single line, + # - should be no longer than 1024 characters. + # Disabling this procedure will allow simple keys of any length and + # height (may cause problems if indentation is broken though). + for level in list(self.possible_simple_keys): + key = self.possible_simple_keys[level] + if key.line != self.line \ + or self.index-key.index > 1024: + if key.required: + raise ScannerError("while scanning a simple key", key.mark, + "could not find expected ':'", self.get_mark()) + del self.possible_simple_keys[level] + + def save_possible_simple_key(self): + # The next token may start a simple key. We check if it's possible + # and save its position. This function is called for + # ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'. + + # Check if a simple key is required at the current position. + required = not self.flow_level and self.indent == self.column + + # The next token might be a simple key. Let's save it's number and + # position. + if self.allow_simple_key: + self.remove_possible_simple_key() + token_number = self.tokens_taken+len(self.tokens) + key = SimpleKey(token_number, required, + self.index, self.line, self.column, self.get_mark()) + self.possible_simple_keys[self.flow_level] = key + + def remove_possible_simple_key(self): + # Remove the saved possible key position at the current flow level. + if self.flow_level in self.possible_simple_keys: + key = self.possible_simple_keys[self.flow_level] + + if key.required: + raise ScannerError("while scanning a simple key", key.mark, + "could not find expected ':'", self.get_mark()) + + del self.possible_simple_keys[self.flow_level] + + # Indentation functions. + + def unwind_indent(self, column): + + ## In flow context, tokens should respect indentation. + ## Actually the condition should be `self.indent >= column` according to + ## the spec. But this condition will prohibit intuitively correct + ## constructions such as + ## key : { + ## } + #if self.flow_level and self.indent > column: + # raise ScannerError(None, None, + # "invalid indentation or unclosed '[' or '{'", + # self.get_mark()) + + # In the flow context, indentation is ignored. We make the scanner less + # restrictive then specification requires. + if self.flow_level: + return + + # In block context, we may need to issue the BLOCK-END tokens. + while self.indent > column: + mark = self.get_mark() + self.indent = self.indents.pop() + self.tokens.append(BlockEndToken(mark, mark)) + + def add_indent(self, column): + # Check if we need to increase indentation. + if self.indent < column: + self.indents.append(self.indent) + self.indent = column + return True + return False + + # Fetchers. + + def fetch_stream_start(self): + # We always add STREAM-START as the first token and STREAM-END as the + # last token. + + # Read the token. + mark = self.get_mark() + + # Add STREAM-START. + self.tokens.append(StreamStartToken(mark, mark, + encoding=self.encoding)) + + + def fetch_stream_end(self): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + self.possible_simple_keys = {} + + # Read the token. + mark = self.get_mark() + + # Add STREAM-END. + self.tokens.append(StreamEndToken(mark, mark)) + + # The steam is finished. + self.done = True + + def fetch_directive(self): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + + # Scan and add DIRECTIVE. + self.tokens.append(self.scan_directive()) + + def fetch_document_start(self): + self.fetch_document_indicator(DocumentStartToken) + + def fetch_document_end(self): + self.fetch_document_indicator(DocumentEndToken) + + def fetch_document_indicator(self, TokenClass): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. Note that there could not be a block collection + # after '---'. + self.remove_possible_simple_key() + self.allow_simple_key = False + + # Add DOCUMENT-START or DOCUMENT-END. + start_mark = self.get_mark() + self.forward(3) + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_start(self): + self.fetch_flow_collection_start(FlowSequenceStartToken) + + def fetch_flow_mapping_start(self): + self.fetch_flow_collection_start(FlowMappingStartToken) + + def fetch_flow_collection_start(self, TokenClass): + + # '[' and '{' may start a simple key. + self.save_possible_simple_key() + + # Increase the flow level. + self.flow_level += 1 + + # Simple keys are allowed after '[' and '{'. + self.allow_simple_key = True + + # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_end(self): + self.fetch_flow_collection_end(FlowSequenceEndToken) + + def fetch_flow_mapping_end(self): + self.fetch_flow_collection_end(FlowMappingEndToken) + + def fetch_flow_collection_end(self, TokenClass): + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Decrease the flow level. + self.flow_level -= 1 + + # No simple keys after ']' or '}'. + self.allow_simple_key = False + + # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_entry(self): + + # Simple keys are allowed after ','. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add FLOW-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(FlowEntryToken(start_mark, end_mark)) + + def fetch_block_entry(self): + + # Block context needs additional checks. + if not self.flow_level: + + # Are we allowed to start a new entry? + if not self.allow_simple_key: + raise ScannerError(None, None, + "sequence entries are not allowed here", + self.get_mark()) + + # We may need to add BLOCK-SEQUENCE-START. + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockSequenceStartToken(mark, mark)) + + # It's an error for the block entry to occur in the flow context, + # but we let the parser detect this. + else: + pass + + # Simple keys are allowed after '-'. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add BLOCK-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(BlockEntryToken(start_mark, end_mark)) + + def fetch_key(self): + + # Block context needs additional checks. + if not self.flow_level: + + # Are we allowed to start a key (not necessary a simple)? + if not self.allow_simple_key: + raise ScannerError(None, None, + "mapping keys are not allowed here", + self.get_mark()) + + # We may need to add BLOCK-MAPPING-START. + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockMappingStartToken(mark, mark)) + + # Simple keys are allowed after '?' in the block context. + self.allow_simple_key = not self.flow_level + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add KEY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(KeyToken(start_mark, end_mark)) + + def fetch_value(self): + + # Do we determine a simple key? + if self.flow_level in self.possible_simple_keys: + + # Add KEY. + key = self.possible_simple_keys[self.flow_level] + del self.possible_simple_keys[self.flow_level] + self.tokens.insert(key.token_number-self.tokens_taken, + KeyToken(key.mark, key.mark)) + + # If this key starts a new block mapping, we need to add + # BLOCK-MAPPING-START. + if not self.flow_level: + if self.add_indent(key.column): + self.tokens.insert(key.token_number-self.tokens_taken, + BlockMappingStartToken(key.mark, key.mark)) + + # There cannot be two simple keys one after another. + self.allow_simple_key = False + + # It must be a part of a complex key. + else: + + # Block context needs additional checks. + # (Do we really need them? They will be caught by the parser + # anyway.) + if not self.flow_level: + + # We are allowed to start a complex value if and only if + # we can start a simple key. + if not self.allow_simple_key: + raise ScannerError(None, None, + "mapping values are not allowed here", + self.get_mark()) + + # If this value starts a new block mapping, we need to add + # BLOCK-MAPPING-START. It will be detected as an error later by + # the parser. + if not self.flow_level: + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockMappingStartToken(mark, mark)) + + # Simple keys are allowed after ':' in the block context. + self.allow_simple_key = not self.flow_level + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add VALUE. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(ValueToken(start_mark, end_mark)) + + def fetch_alias(self): + + # ALIAS could be a simple key. + self.save_possible_simple_key() + + # No simple keys after ALIAS. + self.allow_simple_key = False + + # Scan and add ALIAS. + self.tokens.append(self.scan_anchor(AliasToken)) + + def fetch_anchor(self): + + # ANCHOR could start a simple key. + self.save_possible_simple_key() + + # No simple keys after ANCHOR. + self.allow_simple_key = False + + # Scan and add ANCHOR. + self.tokens.append(self.scan_anchor(AnchorToken)) + + def fetch_tag(self): + + # TAG could start a simple key. + self.save_possible_simple_key() + + # No simple keys after TAG. + self.allow_simple_key = False + + # Scan and add TAG. + self.tokens.append(self.scan_tag()) + + def fetch_literal(self): + self.fetch_block_scalar(style='|') + + def fetch_folded(self): + self.fetch_block_scalar(style='>') + + def fetch_block_scalar(self, style): + + # A simple key may follow a block scalar. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Scan and add SCALAR. + self.tokens.append(self.scan_block_scalar(style)) + + def fetch_single(self): + self.fetch_flow_scalar(style='\'') + + def fetch_double(self): + self.fetch_flow_scalar(style='"') + + def fetch_flow_scalar(self, style): + + # A flow scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after flow scalars. + self.allow_simple_key = False + + # Scan and add SCALAR. + self.tokens.append(self.scan_flow_scalar(style)) + + def fetch_plain(self): + + # A plain scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after plain scalars. But note that `scan_plain` will + # change this flag if the scan is finished at the beginning of the + # line. + self.allow_simple_key = False + + # Scan and add SCALAR. May change `allow_simple_key`. + self.tokens.append(self.scan_plain()) + + # Checkers. + + def check_directive(self): + + # DIRECTIVE: ^ '%' ... + # The '%' indicator is already checked. + if self.column == 0: + return True + + def check_document_start(self): + + # DOCUMENT-START: ^ '---' (' '|'\n') + if self.column == 0: + if self.prefix(3) == '---' \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return True + + def check_document_end(self): + + # DOCUMENT-END: ^ '...' (' '|'\n') + if self.column == 0: + if self.prefix(3) == '...' \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return True + + def check_block_entry(self): + + # BLOCK-ENTRY: '-' (' '|'\n') + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_key(self): + + # KEY(flow context): '?' + if self.flow_level: + return True + + # KEY(block context): '?' (' '|'\n') + else: + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_value(self): + + # VALUE(flow context): ':' + if self.flow_level: + return True + + # VALUE(block context): ':' (' '|'\n') + else: + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_plain(self): + + # A plain scalar may start with any non-space character except: + # '-', '?', ':', ',', '[', ']', '{', '}', + # '#', '&', '*', '!', '|', '>', '\'', '\"', + # '%', '@', '`'. + # + # It may also start with + # '-', '?', ':' + # if it is followed by a non-space character. + # + # Note that we limit the last rule to the block context (except the + # '-' character) because we want the flow context to be space + # independent. + ch = self.peek() + return ch not in '\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'\"%@`' \ + or (self.peek(1) not in '\0 \t\r\n\x85\u2028\u2029' + and (ch == '-' or (not self.flow_level and ch in '?:'))) + + # Scanners. + + def scan_to_next_token(self): + # We ignore spaces, line breaks and comments. + # If we find a line break in the block context, we set the flag + # `allow_simple_key` on. + # The byte order mark is stripped if it's the first character in the + # stream. We do not yet support BOM inside the stream as the + # specification requires. Any such mark will be considered as a part + # of the document. + # + # TODO: We need to make tab handling rules more sane. A good rule is + # Tabs cannot precede tokens + # BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END, + # KEY(block), VALUE(block), BLOCK-ENTRY + # So the checking code is + # if : + # self.allow_simple_keys = False + # We also need to add the check for `allow_simple_keys == True` to + # `unwind_indent` before issuing BLOCK-END. + # Scanners for block, flow, and plain scalars need to be modified. + + if self.index == 0 and self.peek() == '\uFEFF': + self.forward() + found = False + while not found: + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + if self.scan_line_break(): + if not self.flow_level: + self.allow_simple_key = True + else: + found = True + + def scan_directive(self): + # See the specification for details. + start_mark = self.get_mark() + self.forward() + name = self.scan_directive_name(start_mark) + value = None + if name == 'YAML': + value = self.scan_yaml_directive_value(start_mark) + end_mark = self.get_mark() + elif name == 'TAG': + value = self.scan_tag_directive_value(start_mark) + end_mark = self.get_mark() + else: + end_mark = self.get_mark() + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + self.scan_directive_ignored_line(start_mark) + return DirectiveToken(name, value, start_mark, end_mark) + + def scan_directive_name(self, start_mark): + # See the specification for details. + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if not length: + raise ScannerError("while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + value = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + return value + + def scan_yaml_directive_value(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + major = self.scan_yaml_directive_number(start_mark) + if self.peek() != '.': + raise ScannerError("while scanning a directive", start_mark, + "expected a digit or '.', but found %r" % self.peek(), + self.get_mark()) + self.forward() + minor = self.scan_yaml_directive_number(start_mark) + if self.peek() not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected a digit or ' ', but found %r" % self.peek(), + self.get_mark()) + return (major, minor) + + def scan_yaml_directive_number(self, start_mark): + # See the specification for details. + ch = self.peek() + if not ('0' <= ch <= '9'): + raise ScannerError("while scanning a directive", start_mark, + "expected a digit, but found %r" % ch, self.get_mark()) + length = 0 + while '0' <= self.peek(length) <= '9': + length += 1 + value = int(self.prefix(length)) + self.forward(length) + return value + + def scan_tag_directive_value(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + handle = self.scan_tag_directive_handle(start_mark) + while self.peek() == ' ': + self.forward() + prefix = self.scan_tag_directive_prefix(start_mark) + return (handle, prefix) + + def scan_tag_directive_handle(self, start_mark): + # See the specification for details. + value = self.scan_tag_handle('directive', start_mark) + ch = self.peek() + if ch != ' ': + raise ScannerError("while scanning a directive", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + return value + + def scan_tag_directive_prefix(self, start_mark): + # See the specification for details. + value = self.scan_tag_uri('directive', start_mark) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + return value + + def scan_directive_ignored_line(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + ch = self.peek() + if ch not in '\0\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected a comment or a line break, but found %r" + % ch, self.get_mark()) + self.scan_line_break() + + def scan_anchor(self, TokenClass): + # The specification does not restrict characters for anchors and + # aliases. This may lead to problems, for instance, the document: + # [ *alias, value ] + # can be interpreted in two ways, as + # [ "value" ] + # and + # [ *alias , "value" ] + # Therefore we restrict aliases to numbers and ASCII letters. + start_mark = self.get_mark() + indicator = self.peek() + if indicator == '*': + name = 'alias' + else: + name = 'anchor' + self.forward() + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if not length: + raise ScannerError("while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + value = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch not in '\0 \t\r\n\x85\u2028\u2029?:,]}%@`': + raise ScannerError("while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + end_mark = self.get_mark() + return TokenClass(value, start_mark, end_mark) + + def scan_tag(self): + # See the specification for details. + start_mark = self.get_mark() + ch = self.peek(1) + if ch == '<': + handle = None + self.forward(2) + suffix = self.scan_tag_uri('tag', start_mark) + if self.peek() != '>': + raise ScannerError("while parsing a tag", start_mark, + "expected '>', but found %r" % self.peek(), + self.get_mark()) + self.forward() + elif ch in '\0 \t\r\n\x85\u2028\u2029': + handle = None + suffix = '!' + self.forward() + else: + length = 1 + use_handle = False + while ch not in '\0 \r\n\x85\u2028\u2029': + if ch == '!': + use_handle = True + break + length += 1 + ch = self.peek(length) + handle = '!' + if use_handle: + handle = self.scan_tag_handle('tag', start_mark) + else: + handle = '!' + self.forward() + suffix = self.scan_tag_uri('tag', start_mark) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a tag", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + value = (handle, suffix) + end_mark = self.get_mark() + return TagToken(value, start_mark, end_mark) + + def scan_block_scalar(self, style): + # See the specification for details. + + if style == '>': + folded = True + else: + folded = False + + chunks = [] + start_mark = self.get_mark() + + # Scan the header. + self.forward() + chomping, increment = self.scan_block_scalar_indicators(start_mark) + self.scan_block_scalar_ignored_line(start_mark) + + # Determine the indentation level and go to the first non-empty line. + min_indent = self.indent+1 + if min_indent < 1: + min_indent = 1 + if increment is None: + breaks, max_indent, end_mark = self.scan_block_scalar_indentation() + indent = max(min_indent, max_indent) + else: + indent = min_indent+increment-1 + breaks, end_mark = self.scan_block_scalar_breaks(indent) + line_break = '' + + # Scan the inner part of the block scalar. + while self.column == indent and self.peek() != '\0': + chunks.extend(breaks) + leading_non_space = self.peek() not in ' \t' + length = 0 + while self.peek(length) not in '\0\r\n\x85\u2028\u2029': + length += 1 + chunks.append(self.prefix(length)) + self.forward(length) + line_break = self.scan_line_break() + breaks, end_mark = self.scan_block_scalar_breaks(indent) + if self.column == indent and self.peek() != '\0': + + # Unfortunately, folding rules are ambiguous. + # + # This is the folding according to the specification: + + if folded and line_break == '\n' \ + and leading_non_space and self.peek() not in ' \t': + if not breaks: + chunks.append(' ') + else: + chunks.append(line_break) + + # This is Clark Evans's interpretation (also in the spec + # examples): + # + #if folded and line_break == '\n': + # if not breaks: + # if self.peek() not in ' \t': + # chunks.append(' ') + # else: + # chunks.append(line_break) + #else: + # chunks.append(line_break) + else: + break + + # Chomp the tail. + if chomping is not False: + chunks.append(line_break) + if chomping is True: + chunks.extend(breaks) + + # We are done. + return ScalarToken(''.join(chunks), False, start_mark, end_mark, + style) + + def scan_block_scalar_indicators(self, start_mark): + # See the specification for details. + chomping = None + increment = None + ch = self.peek() + if ch in '+-': + if ch == '+': + chomping = True + else: + chomping = False + self.forward() + ch = self.peek() + if ch in '0123456789': + increment = int(ch) + if increment == 0: + raise ScannerError("while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark()) + self.forward() + elif ch in '0123456789': + increment = int(ch) + if increment == 0: + raise ScannerError("while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark()) + self.forward() + ch = self.peek() + if ch in '+-': + if ch == '+': + chomping = True + else: + chomping = False + self.forward() + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a block scalar", start_mark, + "expected chomping or indentation indicators, but found %r" + % ch, self.get_mark()) + return chomping, increment + + def scan_block_scalar_ignored_line(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + ch = self.peek() + if ch not in '\0\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a block scalar", start_mark, + "expected a comment or a line break, but found %r" % ch, + self.get_mark()) + self.scan_line_break() + + def scan_block_scalar_indentation(self): + # See the specification for details. + chunks = [] + max_indent = 0 + end_mark = self.get_mark() + while self.peek() in ' \r\n\x85\u2028\u2029': + if self.peek() != ' ': + chunks.append(self.scan_line_break()) + end_mark = self.get_mark() + else: + self.forward() + if self.column > max_indent: + max_indent = self.column + return chunks, max_indent, end_mark + + def scan_block_scalar_breaks(self, indent): + # See the specification for details. + chunks = [] + end_mark = self.get_mark() + while self.column < indent and self.peek() == ' ': + self.forward() + while self.peek() in '\r\n\x85\u2028\u2029': + chunks.append(self.scan_line_break()) + end_mark = self.get_mark() + while self.column < indent and self.peek() == ' ': + self.forward() + return chunks, end_mark + + def scan_flow_scalar(self, style): + # See the specification for details. + # Note that we loose indentation rules for quoted scalars. Quoted + # scalars don't need to adhere indentation because " and ' clearly + # mark the beginning and the end of them. Therefore we are less + # restrictive then the specification requires. We only need to check + # that document separators are not included in scalars. + if style == '"': + double = True + else: + double = False + chunks = [] + start_mark = self.get_mark() + quote = self.peek() + self.forward() + chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) + while self.peek() != quote: + chunks.extend(self.scan_flow_scalar_spaces(double, start_mark)) + chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) + self.forward() + end_mark = self.get_mark() + return ScalarToken(''.join(chunks), False, start_mark, end_mark, + style) + + ESCAPE_REPLACEMENTS = { + '0': '\0', + 'a': '\x07', + 'b': '\x08', + 't': '\x09', + '\t': '\x09', + 'n': '\x0A', + 'v': '\x0B', + 'f': '\x0C', + 'r': '\x0D', + 'e': '\x1B', + ' ': '\x20', + '\"': '\"', + '\\': '\\', + '/': '/', + 'N': '\x85', + '_': '\xA0', + 'L': '\u2028', + 'P': '\u2029', + } + + ESCAPE_CODES = { + 'x': 2, + 'u': 4, + 'U': 8, + } + + def scan_flow_scalar_non_spaces(self, double, start_mark): + # See the specification for details. + chunks = [] + while True: + length = 0 + while self.peek(length) not in '\'\"\\\0 \t\r\n\x85\u2028\u2029': + length += 1 + if length: + chunks.append(self.prefix(length)) + self.forward(length) + ch = self.peek() + if not double and ch == '\'' and self.peek(1) == '\'': + chunks.append('\'') + self.forward(2) + elif (double and ch == '\'') or (not double and ch in '\"\\'): + chunks.append(ch) + self.forward() + elif double and ch == '\\': + self.forward() + ch = self.peek() + if ch in self.ESCAPE_REPLACEMENTS: + chunks.append(self.ESCAPE_REPLACEMENTS[ch]) + self.forward() + elif ch in self.ESCAPE_CODES: + length = self.ESCAPE_CODES[ch] + self.forward() + for k in range(length): + if self.peek(k) not in '0123456789ABCDEFabcdef': + raise ScannerError("while scanning a double-quoted scalar", start_mark, + "expected escape sequence of %d hexdecimal numbers, but found %r" % + (length, self.peek(k)), self.get_mark()) + code = int(self.prefix(length), 16) + chunks.append(chr(code)) + self.forward(length) + elif ch in '\r\n\x85\u2028\u2029': + self.scan_line_break() + chunks.extend(self.scan_flow_scalar_breaks(double, start_mark)) + else: + raise ScannerError("while scanning a double-quoted scalar", start_mark, + "found unknown escape character %r" % ch, self.get_mark()) + else: + return chunks + + def scan_flow_scalar_spaces(self, double, start_mark): + # See the specification for details. + chunks = [] + length = 0 + while self.peek(length) in ' \t': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch == '\0': + raise ScannerError("while scanning a quoted scalar", start_mark, + "found unexpected end of stream", self.get_mark()) + elif ch in '\r\n\x85\u2028\u2029': + line_break = self.scan_line_break() + breaks = self.scan_flow_scalar_breaks(double, start_mark) + if line_break != '\n': + chunks.append(line_break) + elif not breaks: + chunks.append(' ') + chunks.extend(breaks) + else: + chunks.append(whitespaces) + return chunks + + def scan_flow_scalar_breaks(self, double, start_mark): + # See the specification for details. + chunks = [] + while True: + # Instead of checking indentation, we check for document + # separators. + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a quoted scalar", start_mark, + "found unexpected document separator", self.get_mark()) + while self.peek() in ' \t': + self.forward() + if self.peek() in '\r\n\x85\u2028\u2029': + chunks.append(self.scan_line_break()) + else: + return chunks + + def scan_plain(self): + # See the specification for details. + # We add an additional restriction for the flow context: + # plain scalars in the flow context cannot contain ',' or '?'. + # We also keep track of the `allow_simple_key` flag here. + # Indentation rules are loosed for the flow context. + chunks = [] + start_mark = self.get_mark() + end_mark = start_mark + indent = self.indent+1 + # We allow zero indentation for scalars, but then we need to check for + # document separators at the beginning of the line. + #if indent == 0: + # indent = 1 + spaces = [] + while True: + length = 0 + if self.peek() == '#': + break + while True: + ch = self.peek(length) + if ch in '\0 \t\r\n\x85\u2028\u2029' \ + or (ch == ':' and + self.peek(length+1) in '\0 \t\r\n\x85\u2028\u2029' + + (u',[]{}' if self.flow_level else u''))\ + or (self.flow_level and ch in ',?[]{}'): + break + length += 1 + if length == 0: + break + self.allow_simple_key = False + chunks.extend(spaces) + chunks.append(self.prefix(length)) + self.forward(length) + end_mark = self.get_mark() + spaces = self.scan_plain_spaces(indent, start_mark) + if not spaces or self.peek() == '#' \ + or (not self.flow_level and self.column < indent): + break + return ScalarToken(''.join(chunks), True, start_mark, end_mark) + + def scan_plain_spaces(self, indent, start_mark): + # See the specification for details. + # The specification is really confusing about tabs in plain scalars. + # We just forbid them completely. Do not use tabs in YAML! + chunks = [] + length = 0 + while self.peek(length) in ' ': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch in '\r\n\x85\u2028\u2029': + line_break = self.scan_line_break() + self.allow_simple_key = True + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return + breaks = [] + while self.peek() in ' \r\n\x85\u2028\u2029': + if self.peek() == ' ': + self.forward() + else: + breaks.append(self.scan_line_break()) + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return + if line_break != '\n': + chunks.append(line_break) + elif not breaks: + chunks.append(' ') + chunks.extend(breaks) + elif whitespaces: + chunks.append(whitespaces) + return chunks + + def scan_tag_handle(self, name, start_mark): + # See the specification for details. + # For some strange reasons, the specification does not allow '_' in + # tag handles. I have allowed it anyway. + ch = self.peek() + if ch != '!': + raise ScannerError("while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch, self.get_mark()) + length = 1 + ch = self.peek(length) + if ch != ' ': + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if ch != '!': + self.forward(length) + raise ScannerError("while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch, self.get_mark()) + length += 1 + value = self.prefix(length) + self.forward(length) + return value + + def scan_tag_uri(self, name, start_mark): + # See the specification for details. + # Note: we do not check if URI is well-formed. + chunks = [] + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?:@&=+$,_.!~*\'()[]%': + if ch == '%': + chunks.append(self.prefix(length)) + self.forward(length) + length = 0 + chunks.append(self.scan_uri_escapes(name, start_mark)) + else: + length += 1 + ch = self.peek(length) + if length: + chunks.append(self.prefix(length)) + self.forward(length) + length = 0 + if not chunks: + raise ScannerError("while parsing a %s" % name, start_mark, + "expected URI, but found %r" % ch, self.get_mark()) + return ''.join(chunks) + + def scan_uri_escapes(self, name, start_mark): + # See the specification for details. + codes = [] + mark = self.get_mark() + while self.peek() == '%': + self.forward() + for k in range(2): + if self.peek(k) not in '0123456789ABCDEFabcdef': + raise ScannerError("while scanning a %s" % name, start_mark, + "expected URI escape sequence of 2 hexdecimal numbers, but found %r" + % self.peek(k), self.get_mark()) + codes.append(int(self.prefix(2), 16)) + self.forward(2) + try: + value = bytes(codes).decode('utf-8') + except UnicodeDecodeError as exc: + raise ScannerError("while scanning a %s" % name, start_mark, str(exc), mark) + return value + + def scan_line_break(self): + # Transforms: + # '\r\n' : '\n' + # '\r' : '\n' + # '\n' : '\n' + # '\x85' : '\n' + # '\u2028' : '\u2028' + # '\u2029 : '\u2029' + # default : '' + ch = self.peek() + if ch in '\r\n\x85': + if self.prefix(2) == '\r\n': + self.forward(2) + else: + self.forward() + return '\n' + elif ch in '\u2028\u2029': + self.forward() + return ch + return '' diff --git a/robot/lib/python3.8/site-packages/yaml/serializer.py b/robot/lib/python3.8/site-packages/yaml/serializer.py new file mode 100644 index 0000000000000000000000000000000000000000..fe911e67ae7a739abb491fbbc6834b9c37bbda4b --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/serializer.py @@ -0,0 +1,111 @@ + +__all__ = ['Serializer', 'SerializerError'] + +from .error import YAMLError +from .events import * +from .nodes import * + +class SerializerError(YAMLError): + pass + +class Serializer: + + ANCHOR_TEMPLATE = 'id%03d' + + def __init__(self, encoding=None, + explicit_start=None, explicit_end=None, version=None, tags=None): + self.use_encoding = encoding + self.use_explicit_start = explicit_start + self.use_explicit_end = explicit_end + self.use_version = version + self.use_tags = tags + self.serialized_nodes = {} + self.anchors = {} + self.last_anchor_id = 0 + self.closed = None + + def open(self): + if self.closed is None: + self.emit(StreamStartEvent(encoding=self.use_encoding)) + self.closed = False + elif self.closed: + raise SerializerError("serializer is closed") + else: + raise SerializerError("serializer is already opened") + + def close(self): + if self.closed is None: + raise SerializerError("serializer is not opened") + elif not self.closed: + self.emit(StreamEndEvent()) + self.closed = True + + #def __del__(self): + # self.close() + + def serialize(self, node): + if self.closed is None: + raise SerializerError("serializer is not opened") + elif self.closed: + raise SerializerError("serializer is closed") + self.emit(DocumentStartEvent(explicit=self.use_explicit_start, + version=self.use_version, tags=self.use_tags)) + self.anchor_node(node) + self.serialize_node(node, None, None) + self.emit(DocumentEndEvent(explicit=self.use_explicit_end)) + self.serialized_nodes = {} + self.anchors = {} + self.last_anchor_id = 0 + + def anchor_node(self, node): + if node in self.anchors: + if self.anchors[node] is None: + self.anchors[node] = self.generate_anchor(node) + else: + self.anchors[node] = None + if isinstance(node, SequenceNode): + for item in node.value: + self.anchor_node(item) + elif isinstance(node, MappingNode): + for key, value in node.value: + self.anchor_node(key) + self.anchor_node(value) + + def generate_anchor(self, node): + self.last_anchor_id += 1 + return self.ANCHOR_TEMPLATE % self.last_anchor_id + + def serialize_node(self, node, parent, index): + alias = self.anchors[node] + if node in self.serialized_nodes: + self.emit(AliasEvent(alias)) + else: + self.serialized_nodes[node] = True + self.descend_resolver(parent, index) + if isinstance(node, ScalarNode): + detected_tag = self.resolve(ScalarNode, node.value, (True, False)) + default_tag = self.resolve(ScalarNode, node.value, (False, True)) + implicit = (node.tag == detected_tag), (node.tag == default_tag) + self.emit(ScalarEvent(alias, node.tag, implicit, node.value, + style=node.style)) + elif isinstance(node, SequenceNode): + implicit = (node.tag + == self.resolve(SequenceNode, node.value, True)) + self.emit(SequenceStartEvent(alias, node.tag, implicit, + flow_style=node.flow_style)) + index = 0 + for item in node.value: + self.serialize_node(item, node, index) + index += 1 + self.emit(SequenceEndEvent()) + elif isinstance(node, MappingNode): + implicit = (node.tag + == self.resolve(MappingNode, node.value, True)) + self.emit(MappingStartEvent(alias, node.tag, implicit, + flow_style=node.flow_style)) + for key, value in node.value: + self.serialize_node(key, node, None) + self.serialize_node(value, node, key) + self.emit(MappingEndEvent()) + self.ascend_resolver() + diff --git a/robot/lib/python3.8/site-packages/yaml/tokens.py b/robot/lib/python3.8/site-packages/yaml/tokens.py new file mode 100644 index 0000000000000000000000000000000000000000..4d0b48a394ac8c019b401516a12f688df361cf90 --- /dev/null +++ b/robot/lib/python3.8/site-packages/yaml/tokens.py @@ -0,0 +1,104 @@ + +class Token(object): + def __init__(self, start_mark, end_mark): + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + attributes = [key for key in self.__dict__ + if not key.endswith('_mark')] + attributes.sort() + arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) + for key in attributes]) + return '%s(%s)' % (self.__class__.__name__, arguments) + +#class BOMToken(Token): +# id = '' + +class DirectiveToken(Token): + id = '' + def __init__(self, name, value, start_mark, end_mark): + self.name = name + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class DocumentStartToken(Token): + id = '' + +class DocumentEndToken(Token): + id = '' + +class StreamStartToken(Token): + id = '' + def __init__(self, start_mark=None, end_mark=None, + encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + +class StreamEndToken(Token): + id = '' + +class BlockSequenceStartToken(Token): + id = '' + +class BlockMappingStartToken(Token): + id = '' + +class BlockEndToken(Token): + id = '' + +class FlowSequenceStartToken(Token): + id = '[' + +class FlowMappingStartToken(Token): + id = '{' + +class FlowSequenceEndToken(Token): + id = ']' + +class FlowMappingEndToken(Token): + id = '}' + +class KeyToken(Token): + id = '?' + +class ValueToken(Token): + id = ':' + +class BlockEntryToken(Token): + id = '-' + +class FlowEntryToken(Token): + id = ',' + +class AliasToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class AnchorToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class TagToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class ScalarToken(Token): + id = '' + def __init__(self, value, plain, start_mark, end_mark, style=None): + self.value = value + self.plain = plain + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + diff --git a/robot/pyvenv.cfg b/robot/pyvenv.cfg new file mode 100644 index 0000000000000000000000000000000000000000..b89ae83699598c38c75ea89c31158363490743a4 --- /dev/null +++ b/robot/pyvenv.cfg @@ -0,0 +1,8 @@ +home = /usr +implementation = CPython +version_info = 3.8.5.final.0 +virtualenv = 20.0.17 +include-system-site-packages = false +base-prefix = /usr +base-exec-prefix = /usr +base-executable = /usr/bin/python3