diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2019-10-15 12:17:47 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2019-10-15 13:44:22 +0200 |
| commit | 9bf06a8e8833e9a95b6bac95d57533f2ba724bda (patch) | |
| tree | b34880af2dd3d3a2c9094f4857b184b6fce77a75 /gui/scripts | |
| parent | 2156081a3b5391aac5fa3fe9fa27dcedf9d5b4ba (diff) | |
| download | mullvadvpn-9bf06a8e8833e9a95b6bac95d57533f2ba724bda.tar.xz mullvadvpn-9bf06a8e8833e9a95b6bac95d57533f2ba724bda.zip | |
Convert to python 3
Diffstat (limited to 'gui/scripts')
| -rw-r--r-- | gui/scripts/README.md | 25 | ||||
| -rw-r--r-- | gui/scripts/extract-geo-data.py | 114 | ||||
| -rw-r--r-- | gui/scripts/integrate-into-app.py | 14 | ||||
| -rw-r--r-- | gui/scripts/requirements.txt | 79 |
4 files changed, 136 insertions, 96 deletions
diff --git a/gui/scripts/README.md b/gui/scripts/README.md index 8d94a47687..6d11c55ec0 100644 --- a/gui/scripts/README.md +++ b/gui/scripts/README.md @@ -1,10 +1,10 @@ -This is a folder with the supporting scripts written in python 2, node, bash. +This is a folder with the supporting scripts written in Python 3, node, bash. ## Dependency installation notes 1. Run the following command in terminal to install python dependencies: - `pip install -r requirements.txt` + `pip3 install -r requirements.txt` 2. Run `npm install -g topojson-server` to install `geo2topo` tool which is used by python scripts to convert GeoJSON to TopoJSON @@ -53,7 +53,7 @@ unzip ne_50m_populated_places.zip -d ne_50m_populated_places/ Run the following script to produce a TopoJSON data used by the app: ``` -python extract-geo-data.py +python3 extract-geo-data.py ``` and finally generate the R-Tree cache: @@ -70,5 +70,22 @@ Once you've extracted all the geo data, run the integration script that will copy all files ignoring intermediate ones into the `gui/assets/geo` folder: ``` -python integrate-into-app.py +python3 integrate-into-app.py +``` + +## Locking Python dependencies + +1. Freeze dependencies: + +``` +pip3 freeze -r requirements.txt +``` + +and save the output into `requirements.txt`. + + +2. Hash them with `hashin` tool: + +``` +hashin --python 3.7 --verbose --update-all ``` diff --git a/gui/scripts/extract-geo-data.py b/gui/scripts/extract-geo-data.py index 611e6acffd..e64cb68867 100644 --- a/gui/scripts/extract-geo-data.py +++ b/gui/scripts/extract-geo-data.py @@ -5,7 +5,7 @@ This module forms a geo json of highly populated cities in the world import os from os import path import json -import urllib2 +import urllib.request from subprocess import Popen, PIPE from polib import POFile, POEntry import colorful as c @@ -74,7 +74,7 @@ def extract_cities(): with open(output_path, "w") as f: f.write(json.dumps(my_layer)) - print c.green("Extracted data to {}".format(output_path)) + print(c.green("Extracted data to {}".format(output_path))) def extract_countries(): @@ -106,7 +106,7 @@ def extract_countries(): with open(output_path, "w") as f: f.write(json.dumps(my_layer)) - print c.green("Extracted data to {}".format(output_path)) + print(c.green("Extracted data to {}".format(output_path))) def extract_geometry(): @@ -130,11 +130,11 @@ def extract_geometry(): ['geo2topo', '-q', '1e5', 'geometry=-', '-o', output_path], stdin=PIPE, stdout=PIPE, stderr=PIPE ) - errors = p.communicate(input=json.dumps(my_layer))[1] + errors = p.communicate(input=json.dumps(my_layer).encode())[1] if p.returncode == 0: - print c.green("Extracted data to {}".format(output_path)) + print(c.green("Extracted data to {}".format(output_path))) else: - print c.red("geo2topo exited with {}. {}".format(p.returncode, errors.decode('utf-8').strip())) + print(c.red("geo2topo exited with {}. {}".format(p.returncode, errors.decode().strip()))) def extract_provinces_and_states_lines(): @@ -158,11 +158,11 @@ def extract_provinces_and_states_lines(): ['geo2topo', '-q', '1e5', 'geometry=-', '-o', output_path], stdin=PIPE, stdout=PIPE, stderr=PIPE ) - errors = p.communicate(input=json.dumps(my_layer))[1] + errors = p.communicate(input=json.dumps(my_layer).encode())[1] if p.returncode == 0: - print c.green("Extracted data to {}".format(output_path)) + print(c.green("Extracted data to {}".format(output_path))) else: - print c.red("geo2topo exited with {}. {}".format(p.returncode, errors.decode('utf-8').strip())) + print(c.red("geo2topo exited with {}. {}".format(p.returncode, errors.decode().strip()))) def extract_countries_po(): @@ -181,7 +181,7 @@ def extract_countries_po(): if not path.exists(locale_out_dir): os.makedirs(locale_out_dir) - print "Generating {}".format(output_path) + print("Generating {}".format(output_path)) for feat in source: props = lower_dict_keys(feat["properties"]) @@ -196,11 +196,11 @@ def extract_countries_po(): translated_name = props.get(name_key) elif props.get(name_fallback) is not None: translated_name = props.get(name_fallback) - print c.orange(u"Missing translation for {}".format(translated_name)) + print(c.orange("Missing translation for {}".format(translated_name))) else: raise ValueError( "Cannot find the translation for {}. Probe keys: {}" - .format(locale, (name_key, name_fallback)) + .format(locale, (name_key, name_fallback)) ) entry = POEntry( @@ -235,7 +235,7 @@ def extract_countries_po(): sort_pofile_entries(po) po.save(output_path) - print c.green("Extracted {} countries for {} to {}".format(len(po), locale, output_path)) + print(c.green("Extracted {} countries for {} to {}".format(len(po), locale, output_path))) def extract_cities_po(): @@ -256,7 +256,7 @@ def extract_cities_po(): if not path.exists(locale_out_dir): os.makedirs(locale_out_dir) - print "Generating {}".format(output_path) + print("Generating {}".format(output_path)) with fiona.open(input_path) as source: for feat in source: @@ -271,12 +271,12 @@ def extract_cities_po(): hits += 1 elif props.get(name_fallback) is not None: translated_name = props.get(name_fallback) - print c.orange(u"Missing translation for {}".format(translated_name)) + print(c.orange("Missing translation for {}".format(translated_name))) misses += 1 else: raise ValueError( "Cannot find the translation for {}. Probe keys: {}" - .format(locale, (name_key, name_fallback)) + .format(locale, (name_key, name_fallback)) ) entry = POEntry( @@ -287,11 +287,11 @@ def extract_cities_po(): try: po.append(entry) except ValueError as err: - print c.orange(u"Cannot add an entry: {}".format(err)) + print(c.orange("Cannot add an entry: {}".format(err))) sort_pofile_entries(po) po.save(output_path) - print c.green("Extracted {} cities to {}".format(len(po), output_path)) + print(c.green("Extracted {} cities to {}".format(len(po), output_path))) stats.append((locale, hits, misses)) @@ -299,14 +299,14 @@ def extract_cities_po(): def sort_pofile_entries(pofile): - pofile.sort(key=lambda o: o.msgid_with_context.encode('utf-8')) + pofile.sort(key=lambda o: o.msgid_with_context) def extract_relay_translations(): try: response = request_relays() except Exception as e: - print c.red("Failed to fetch the relays list: {}".format(e)) + print(c.red("Failed to fetch the relays list: {}".format(e))) raise result = response.get("result") @@ -326,18 +326,18 @@ def extract_relay_locations_pot(countries): pot.metadata = {"Content-Type": "text/plain; charset=utf-8"} output_path = path.join(LOCALE_OUT_DIR, RELAY_LOCATIONS_POT_FILENAME) - print "Generating {}".format(output_path) + print("Generating {}".format(output_path)) for country in countries: country_name = country.get("name") if country_name is not None: entry = POEntry( msgid=country_name, - msgstr=u"", + msgstr="", comment=country.get("code").upper() ) pot.append(entry) - print u"{} ({})".format(country_name, country.get("code")).encode('utf-8') + print("{} ({})".format(country_name, country.get("code"))) cities = country.get("cities") if cities is not None: @@ -346,16 +346,16 @@ def extract_relay_locations_pot(countries): if city_name is not None: entry = POEntry( msgid=city_name, - msgstr=u"", - comment=u"{} {}".format(country.get("code").upper(), city.get("code").upper()) + msgstr="", + comment="{} {}".format(country.get("code").upper(), city.get("code").upper()) ) try: pot.append(entry) except ValueError as err: - print c.orange(u"Cannot add an entry: {}".format(err)) + print(c.orange("Cannot add an entry: {}".format(err))) - print u"{} ({})".format(city_name, city.get("code")).encode('utf-8') + print("{} ({})".format(city_name, city.get("code"))) pot.save(output_path) @@ -374,7 +374,7 @@ def prepare_stats_table_column(item): def print_stats_table(title, data): header = ("Locale", "Hits", "Misses", "% translated", "Total") - color_data = map(prepare_stats_table_column, data) + color_data = list(map(prepare_stats_table_column, data)) table = AsciiTable([header] + color_data) table.title = title @@ -382,9 +382,9 @@ def print_stats_table(title, data): for i in range(1, 5): table.justify_columns[i] = 'center' - print "" - print table.table - print "" + print("") + print(table.table) + print("") def translate_relay_locations(countries): @@ -403,7 +403,7 @@ def translate_relay_locations(countries): for locale in os.listdir(LOCALE_DIR): locale_dir = path.join(LOCALE_DIR, locale) if path.isdir(locale_dir): - print "Generating {}".format(path.join(locale, RELAY_LOCATIONS_PO_FILENAME)) + print("Generating {}".format(path.join(locale, RELAY_LOCATIONS_PO_FILENAME))) (hits, misses) = translate_single_relay_locations(country_translator, city_translator, countries, locale) stats.append((locale, hits, misses)) @@ -442,12 +442,11 @@ def translate_single_relay_locations(country_translator, city_translator, countr translated_country_name = "" misses += 1 - log_message = u"{} ({}) -> \"{}\"".format( - country_name, country_code, translated_country_name).encode('utf-8') + log_message = "{} ({}) -> \"{}\"".format(country_name, country_code, translated_country_name) if found_country_translation: - print c.green(log_message) + print(c.green(log_message)) else: - print c.orange(log_message) + print(c.orange(log_message)) # translate country entry = POEntry( @@ -460,8 +459,8 @@ def translate_single_relay_locations(country_translator, city_translator, countr # translate cities cities = country.get("cities") if cities is None: - print c.orange(u"Skip {} ({}) because no cities were found.".format( - country_name, country_code)) + print(c.orange("Skip {} ({}) because no cities were found." + .format(country_name, country_code))) continue for city in cities: @@ -476,7 +475,7 @@ def translate_single_relay_locations(country_translator, city_translator, countr translated_name = city_translator.translate(locale, split[0].strip()) if translated_name is not None and len(split) > 1: - translated_name = u"{}, {}".format(translated_name, split[1].strip()) + translated_name = "{}, {}".format(translated_name, split[1].strip()) else: translated_name = city_translator.translate(locale, city_name) @@ -488,23 +487,22 @@ def translate_single_relay_locations(country_translator, city_translator, countr translated_name = "" misses += 1 - log_message = u"{} ({}) -> \"{}\"".format( - city_name, city_code, translated_name).encode('utf-8') + log_message = "{} ({}) -> \"{}\"".format(city_name, city_code, translated_name) if found_translation: - print c.green(log_message) + print(c.green(log_message)) else: - print c.orange(log_message) + print(c.orange(log_message)) entry = POEntry( msgid=city_name, msgstr=translated_name, - comment=u"{} {}".format(country_code.upper(), city_code.upper()) + comment="{} {}".format(country_code.upper(), city_code.upper()) ) try: po.append(entry) except ValueError as err: - print c.orange(u"Cannot add an entry: {}".format(err)) + print(c.orange("Cannot add an entry: {}".format(err))) po.save(output_path) @@ -513,14 +511,12 @@ def translate_single_relay_locations(country_translator, city_translator, countr ### HELPERS ### -class CountryTranslator(object): +class CountryTranslator: """ This class provides facilities for translating countries """ def __init__(self): - super(CountryTranslator, self).__init__() - self.dataset = self.__build_index() def translate(self, locale, iso_a2): @@ -539,8 +535,8 @@ class CountryTranslator(object): value = props.get(name_key) if value is None: - print c.orange(u"Missing translation for {} ({}) under the {} key".format( - iso_a2, locale, name_key).encode('utf-8')) + print(c.orange("Missing translation for {} ({}) under the {} key" + .format(iso_a2, locale, name_key))) else: return value @@ -567,14 +563,12 @@ class CountryTranslator(object): return dataset -class CityTranslator(object): +class CityTranslator: """ This class provides facilities for translating places from English. """ def __init__(self): - super(CityTranslator, self).__init__() - self.dataset = self.__build_index() def translate(self, locale, english_name): @@ -594,8 +588,8 @@ class CityTranslator(object): value = props.get(name_key) if value is None: - print c.orange(u"Missing translation for {} ({}) under the {} key".format( - english_name, locale, name_key).encode('utf-8')) + print(c.orange("Missing translation for {} ({}) under the {} key" + .format(english_name, locale, name_key))) else: return value @@ -639,7 +633,7 @@ def get_shape_path(dataset_name): def lower_dict_keys(input_dict): - return dict((k.lower(), v) for k, v in input_dict.iteritems()) + return dict((k.lower(), v) for k, v in input_dict.items()) def convert_locale_ident(locale_ident): @@ -664,10 +658,10 @@ def map_locale(locale_ident): def request_relays(): - data = json.dumps({"jsonrpc": "2.0", "id": "0", "method": "relay_list_v3"}) - headers = {"Content-Type": "application/json"} - request = urllib2.Request("https://api.mullvad.net/rpc/", data, headers) - return json.load(urllib2.urlopen(request)) + data = json.dumps({"jsonrpc": "2.0", "id": "0", "method": "relay_list_v3"}).encode() + request = urllib.request.Request("https://api.mullvad.net/rpc/", data=data) + request.add_header("Content-Type", "application/json") + return json.load(urllib.request.urlopen(request)) # Program main() diff --git a/gui/scripts/integrate-into-app.py b/gui/scripts/integrate-into-app.py index ce94da537c..28137a2732 100644 --- a/gui/scripts/integrate-into-app.py +++ b/gui/scripts/integrate-into-app.py @@ -52,7 +52,7 @@ def copy_geo_assets(): src = path.join(GENERATED_CONTENT_OUTPUT_PATH, f) dst = path.join(APP_GEO_ASSETS_PATH, f) - print u"Copying {} to {}".format(src, dst) + print("Copying {} to {}".format(src, dst)) shutil.copyfile(src, dst) @@ -79,14 +79,14 @@ def merge_single_locale_folder(src, dst): dst_po = path.join(dst, f) if f in TRANSLATIONS_TO_COPY: - print u"Copying {} to {}".format(src_po, dst_po) + print("Copying {} to {}".format(src_po, dst_po)) shutil.copyfile(src_po, dst_po) elif f in TRANSLATIONS_TO_MERGE: # merge ../locales/*/file.po with ./out/locales/*/file.po # use existing translation to resolve conflicts merge_gettext_catalogues(dst_po, src_po) else: - print c.orange(u"Unexpected file: {}".format(src_po)) + print(c.orange("Unexpected file: {}".format(src_po))) def merge_gettext_catalogues(existing_catalogue_file, generated_catalogue_file): @@ -109,11 +109,11 @@ def merge_gettext_catalogues(existing_catalogue_file, generated_catalogue_file): (exit_code, errors) = run_program("msgcat", *args) if exit_code == 0: - print c.green(u"Merged {} into {}.".format(generated_catalogue_file, existing_catalogue_file)) + print(c.green("Merged {} into {}.".format(generated_catalogue_file, existing_catalogue_file))) else: - print c.red(u"msgcat exited with {}: {}".format(exit_code, errors.decode('utf-8').strip())) + print(c.red("msgcat exited with {}: {}".format(exit_code, errors.decode().strip()))) else: - print c.orange(u"The existing catalogue does not exist. Copying {} to {}" + print(c.orange("The existing catalogue does not exist. Copying {} to {}") .format(generated_catalogue_file, existing_catalogue_file)) shutil.copyfile(generated_catalogue_file, existing_catalogue_file) @@ -121,7 +121,7 @@ def merge_gettext_catalogues(existing_catalogue_file, generated_catalogue_file): def run_program(*args): p = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) - print u"Run: {}".format(' '.join(args)) + print("Run: {}".format(' '.join(args))) errors = p.communicate()[1] return (p.returncode, errors) diff --git a/gui/scripts/requirements.txt b/gui/scripts/requirements.txt index 4693c81bf8..2624dd6b14 100644 --- a/gui/scripts/requirements.txt +++ b/gui/scripts/requirements.txt @@ -1,33 +1,62 @@ -Fiona==1.8.6 \ - --hash=sha256:1353a0c7d03f96b0755d13bc0277b0b6a79585f94981e1ae6452c55958431b2b \ - --hash=sha256:1783c3abceccca318fac3d5bfd4305bc31c94fbb5abdc60e96b0ccedaa263857 \ - --hash=sha256:22dfc9d82adae78567b2a242f297915aeb7727cde06a3d762252e5393af959c5 \ - --hash=sha256:2e1ba6e87f9a63962af4c4c44152f9f49245c29fd90ce1c4bb19bddbb31e22f4 \ - --hash=sha256:6c2b665ad369768cde426def664a9af46263152731b4a9493380a337f6c5a79e \ - --hash=sha256:7ebfdce0be685aa44c31529d57cdea232c2386ad305c11a329f91c7613dbd2f4 \ - --hash=sha256:8e361b3cf00a472570fdd25ad39c722f1b3fda03fc4de1d35aac2b5da39bcdb3 \ - --hash=sha256:cb9081003e8585a284365b28cff61343541405263fd4e1c79f3baec7321b8b3f \ - --hash=sha256:e3bfabe62c4081c88c47aa10c6165ecfefaa7c9aa5406496b75ff364757a0bbe \ - --hash=sha256:ee2bbe5640f68342b6746a0ba733e25ee221106368558be6cd73dd08fc6fbe04 \ - --hash=sha256:fa31dfe8855b9cd0b128b47a4df558f1b8eda90d2181bff1dd9854e5556efb3e +Fiona==1.8.8 \ + --hash=sha256:2bc68b11580352a10a92da734dc09bd12348e45188d94416788681430ff0f02a \ + --hash=sha256:711c3be73203b37812992089445a1e4e9d3d6b64e667389f7b15406e15a91e83 \ + --hash=sha256:8431aa3d790c5b509c4532ddb9a783df34d02a4e7b3175e5e75d058c09f81a90 Shapely==1.6.4.post2 \ - --hash=sha256:0378964902f89b8dbc332e5bdfa08e0bc2f7ab39fecaeb17fbb2a7699a44fe71 \ - --hash=sha256:34e7c6f41fb27906ccdf2514ee44a5774b90b39a256b6511a6a57d11ffe64999 \ - --hash=sha256:3ca69d4b12e2b05b549465822744b6a3a1095d8488cc27b2728a06d3c07d0eee \ - --hash=sha256:3e9388f29bd81fcd4fa5c35125e1fbd4975ee36971a87a90c093f032d0e9de24 \ --hash=sha256:3ef28e3f20a1c37f5b99ea8cf8dcb58e2f1a8762d65ed2d21fd92bf1d4811182 \ - --hash=sha256:523c94403047eb6cacd7fc1863ebef06e26c04d8a4e7f8f182d49cd206fe787e \ - --hash=sha256:5d22a1a705c2f70f61ccadc696e33d922c1a92e00df8e1d58a6ade14dd7e3b4f \ - --hash=sha256:714b6680215554731389a1bbdae4cec61741aa4726921fa2b2b96a6f578a2534 \ --hash=sha256:7dfe1528650c3f0dc82f41a74cf4f72018288db9bfb75dcd08f6f04233ec7e78 \ - --hash=sha256:ba58b21b9cf3c33725f7f530febff9ed6a6846f9d0bf8a120fc74683ff919f89 \ - --hash=sha256:c4b87bb61fc3de59fc1f85e71a79b0c709dc68364d9584473697aad4aa13240f \ - --hash=sha256:ebb4d2bee7fac3f6c891fcdafaa17f72ab9c6480f6d00de0b2dc9a5137dfe342 + --hash=sha256:c4b87bb61fc3de59fc1f85e71a79b0c709dc68364d9584473697aad4aa13240f polib==1.1.0 \ --hash=sha256:93b730477c16380c9a96726c54016822ff81acfa553977fdd131f2b90ba858d7 \ --hash=sha256:fad87d13696127ffb27ea0882d6182f1a9cf8a5e2b37a587751166c51e5a332a -colorful==0.5.0 \ - --hash=sha256:74bbaeb90fb5b3d31fccdc6e562bdce721f19dc6d566f72a31e43f5503ac2841 \ - --hash=sha256:86db34ba9b41106d7c69a394049e9d47be4cf15f863d04b8a0b21509ea9df6bc +colorful==0.5.4 \ + --hash=sha256:86848ad4e2eda60cd2519d8698945d22f6f6551e23e95f3f14dfbb60997807ea \ + --hash=sha256:8d264b52a39aae4c0ba3e2a46afbaec81b0559a99be0d2cfe2aba4cf94531348 terminaltables==3.1.0 \ --hash=sha256:f3eb0eb92e3833972ac36796293ca0906e998dc3be91fbe1f8615b331b853b81 +## The following requirements were added by pip freeze: +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +cairocffi==1.1.0 \ + --hash=sha256:f1c0c5878f74ac9ccb5d48b2601fcc75390c881ce476e79f4cfedd288b1b05db +CairoSVG==2.4.2 \ + --hash=sha256:4e668f96653326780036ebb0a9ff2bb59a8443d7bcfc51a14aab77b57a8e67ad \ + --hash=sha256:9cb1df7e9bc60f75fb87f67940a8fb805aad544337a67a40b67c05cfe33711a2 +cffi==1.13.0 \ + --hash=sha256:8fe230f612c18af1df6f348d02d682fe2c28ca0a6c3856c99599cdacae7cf226 +click==7.0 \ + --hash=sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13 \ + --hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7 +click-plugins==1.1.1 \ + --hash=sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b \ + --hash=sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8 +cligj==0.5.0 \ + --hash=sha256:20f24ce9abfde3f758aec3399e6811b936b6772f360846c662c19bf5537b4f14 \ + --hash=sha256:6c7d52d529a78712491974f975c33473f430c0f7beb18c0d7a402a743dcb460a +cssselect2==0.2.2 \ + --hash=sha256:07e9c3b1b52d81dd08b177532bbd6b9ced650d87abfd641f4e4ec7de34b98807 \ + --hash=sha256:70485a680cd72b023f0ce5ae4dcd392e2b10f7280e20afdb1735334bd6af7e6a +defusedxml==0.6.0 \ + --hash=sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93 \ + --hash=sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5 +munch==2.3.2 \ + --hash=sha256:6ae3d26b837feacf732fb8aa5b842130da1daf221f5af9f9d4b2a0a6414b0d51 +Pillow==6.2.0 \ + --hash=sha256:2ed9c4f694861642401f27dc3cb99772be67cd190e84845c749dae0a06c3bfae \ + --hash=sha256:4548236844327a718ce3bb182ab32a16fa2050c61e334e959f554cac052fb0df \ + --hash=sha256:65a28969a025a0eb4594637b6103201dc4ed2a9508bdab56ac33e43e3081c404 \ + --hash=sha256:972a7aaeb7c4a2795b52eef52ee991ef040b31009f36deca6207a986607b55f3 \ + --hash=sha256:9ba37698e242223f8053cc158f130aee046a96feacbeab65893dbe94f5530118 \ + --hash=sha256:f1baa54d50ec031d1a9beb89974108f8f2c0706f49798f4777df879df0e1adb6 +pycparser==2.19 \ + --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +tinycss2==1.0.2 \ + --hash=sha256:6427d0e3faa0a5e0e8c9f6437e2de26148a7a197a8b0992789f23d9a802788cf \ + --hash=sha256:9fdacc0e22d344ddd2ca053837c133900fe820ae1222f63b79617490a498507a +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 |
